use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, Level};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};

use crate::{LateContext, LateLintPass};

declare_lint! {
    /// The `default_overrides_default_fields` lint checks for manual `impl` blocks of the
    /// `Default` trait of types with default field values.
    ///
    /// ### Example
    ///
    /// ```rust,compile_fail
    /// #![feature(default_field_values)]
    /// struct Foo {
    ///     x: i32 = 101,
    ///     y: NonDefault,
    /// }
    ///
    /// struct NonDefault;
    ///
    /// #[deny(default_overrides_default_fields)]
    /// impl Default for Foo {
    ///     fn default() -> Foo {
    ///         Foo { x: 100, y: NonDefault }
    ///     }
    /// }
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// Manually writing a `Default` implementation for a type that has
    /// default field values runs the risk of diverging behavior between
    /// `Type { .. }` and `<Type as Default>::default()`, which would be a
    /// foot-gun for users of that type that would expect these to be
    /// equivalent. If `Default` can't be derived due to some fields not
    /// having a `Default` implementation, we encourage the use of `..` for
    /// the fields that do have a default field value.
    pub DEFAULT_OVERRIDES_DEFAULT_FIELDS,
    Deny,
    "detect `Default` impl that should use the type's default field values",
    @feature_gate = default_field_values;
}

declare_lint_pass!(DefaultCouldBeDerived => [DEFAULT_OVERRIDES_DEFAULT_FIELDS]);

impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived {
    fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
        // Look for manual implementations of `Default`.
        let hir::ImplItemImplKind::Trait { trait_item_def_id, .. } = impl_item.impl_kind else {
            return;
        };
        if !trait_item_def_id.is_ok_and(|id| cx.tcx.is_diagnostic_item(sym::default_fn, id)) {
            return;
        }
        let hir::ImplItemKind::Fn(_sig, body_id) = impl_item.kind else { return };
        let impl_id = cx.tcx.local_parent(impl_item.owner_id.def_id);
        if cx.tcx.is_automatically_derived(impl_id.to_def_id()) {
            // We don't care about what `#[derive(Default)]` produces in this lint.
            return;
        }
        let ty = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip();
        let ty::Adt(def, _) = ty.kind() else { return };

        // We now know we have a manually written definition of a `<Type as Default>::default()`.

        let type_def_id = def.did();
        let body = cx.tcx.hir_body(body_id);

        // FIXME: evaluate bodies with statements and evaluate bindings to see if they would be
        // derivable.
        let hir::ExprKind::Block(hir::Block { stmts: _, expr: Some(expr), .. }, None) =
            body.value.kind
        else {
            return;
        };

        // Keep a mapping of field name to `hir::FieldDef` for every field in the type. We'll use
        // these to check for things like checking whether it has a default or using its span for
        // suggestions.
        let orig_fields = match cx.tcx.hir_get_if_local(type_def_id) {
            Some(hir::Node::Item(hir::Item {
                kind:
                    hir::ItemKind::Struct(
                        _,
                        _generics,
                        hir::VariantData::Struct { fields, recovered: _ },
                    ),
                ..
            })) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(),
            _ => return,
        };

        // We check `fn default()` body is a single ADT literal and get all the fields that are
        // being set.
        let hir::ExprKind::Struct(_qpath, fields, tail) = expr.kind else { return };

        // We have a struct literal
        //
        // struct Foo {
        //     field: Type,
        // }
        //
        // impl Default for Foo {
        //     fn default() -> Foo {
        //         Foo {
        //             field: val,
        //         }
        //     }
        // }
        //
        // We would suggest `#[derive(Default)]` if `field` has a default value, regardless of what
        // it is; we don't want to encourage divergent behavior between `Default::default()` and
        // `..`.

        if let hir::StructTailExpr::Base(_) = tail {
            // This is *very* niche. We'd only get here if someone wrote
            // impl Default for Ty {
            //     fn default() -> Ty {
            //         Ty { ..something() }
            //     }
            // }
            // where `something()` would have to be a call or path.
            // We have nothing meaningful to do with this.
            return;
        }

        // At least one of the fields with a default value have been overridden in
        // the `Default` implementation. We suggest removing it and relying on `..`
        // instead.
        let any_default_field_given =
            fields.iter().any(|f| orig_fields.get(&f.ident.name).and_then(|f| f.default).is_some());

        if !any_default_field_given {
            // None of the default fields were actually provided explicitly, so the manual impl
            // doesn't override them (the user used `..`), so there's no risk of divergent behavior.
            return;
        }

        let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id);
        let span = cx.tcx.hir_span_with_body(hir_id);
        cx.tcx.emit_node_span_lint(
            DEFAULT_OVERRIDES_DEFAULT_FIELDS,
            hir_id,
            span,
            WrongDefaultImpl { tcx: cx.tcx, type_def_id, orig_fields, fields, impl_span: span },
        );
    }
}

struct WrongDefaultImpl<'a, 'tcx> {
    tcx: TyCtxt<'tcx>,
    type_def_id: DefId,
    orig_fields: FxHashMap<Symbol, &'a hir::FieldDef<'tcx>>,
    fields: &'a [hir::ExprField<'tcx>],
    impl_span: Span,
}

impl<'a, 'b, 'tcx> Diagnostic<'a, ()> for WrongDefaultImpl<'b, 'tcx> {
    fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
        let Self { tcx, type_def_id, orig_fields, fields, impl_span } = self;
        let mut diag =
            Diag::new(dcx, level, "`Default` impl doesn't use the declared default field values");

        // For each field in the struct expression
        //   - if the field in the type has a default value, it should be removed
        //   - elif the field is an expression that could be a default value, it should be used as the
        //     field's default value (FIXME: not done).
        //   - else, we wouldn't touch this field, it would remain in the manual impl
        let mut removed_all_fields = true;
        for field in fields {
            if orig_fields.get(&field.ident.name).and_then(|f| f.default).is_some() {
                diag.span_label(field.expr.span, "this field has a default value");
            } else {
                removed_all_fields = false;
            }
        }

        if removed_all_fields {
            diag.multipart_suggestion(
                "to avoid divergence in behavior between `Struct { .. }` and \
                 `<Struct as Default>::default()`, derive the `Default`",
                vec![
                    (tcx.def_span(type_def_id).shrink_to_lo(), "#[derive(Default)] ".to_string()),
                    (impl_span, String::new()),
                ],
                Applicability::MachineApplicable,
            );
        } else {
            diag.help(
                "use the default values in the `impl` with `Struct { mandatory_field, .. }` to \
                       avoid them diverging over time",
            );
        }
        diag
    }
}
