use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;

declare_clippy_lint! {
    /// ### What it does
    /// Suggests removing the use of a `map()` (or `map_err()`) method when an `Option` or `Result`
    /// is being constructed.
    ///
    /// ### Why is this bad?
    /// It introduces unnecessary complexity. Instead, the function can be called before
    /// constructing the `Option` or `Result` from its return value.
    ///
    /// ### Example
    /// ```no_run
    /// Some(4).map(i32::swap_bytes)
    /// # ;
    /// ```
    /// Use instead:
    /// ```no_run
    /// Some(i32::swap_bytes(4))
    /// # ;
    /// ```
    #[clippy::version = "1.74.0"]
    pub UNNECESSARY_MAP_ON_CONSTRUCTOR,
    complexity,
    "using `map`/`map_err` on `Option` or `Result` constructors"
}
declare_lint_pass!(UnnecessaryMapOnConstructor => [UNNECESSARY_MAP_ON_CONSTRUCTOR]);

impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
        if expr.span.from_expansion() {
            return;
        }
        if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind
            && let Some(sym::Option | sym::Result) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx)
        {
            let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind
                && let hir::ExprKind::Path(constructor_path) = constructor.kind
            {
                if constructor.span.from_expansion() || arg.span.from_expansion() {
                    return;
                }
                (constructor_path, arg)
            } else {
                return;
            };
            let constructor_symbol = match constructor_path {
                hir::QPath::Resolved(_, path) => {
                    if let Some(path_segment) = path.segments.last() {
                        path_segment.ident.name
                    } else {
                        return;
                    }
                },
                hir::QPath::TypeRelative(_, path) => path.ident.name,
                hir::QPath::LangItem(..) => return,
            };
            match constructor_symbol {
                sym::Some | sym::Ok if path.ident.name == sym::map => (),
                sym::Err if path.ident.name == sym::map_err => (),
                _ => return,
            }

            if let hir::ExprKind::Path(fun) = map_arg.kind {
                if map_arg.span.from_expansion() {
                    return;
                }
                let mut applicability = Applicability::MachineApplicable;
                let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability);
                let constructor_snippet =
                    snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability);
                let constructor_arg_snippet =
                    snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability);
                span_lint_and_sugg(
                    cx,
                    UNNECESSARY_MAP_ON_CONSTRUCTOR,
                    expr.span,
                    format!(
                        "unnecessary {} on constructor {constructor_snippet}(_)",
                        path.ident.name
                    ),
                    "try",
                    format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"),
                    applicability,
                );
            }
        }
    }
}
