use super::utils::make_iterator_snippet;
use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Expr, ExprKind, Pat, PatKind, StmtKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Span;

/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
/// iterator element is used.
pub(super) fn 
<'tcx>(
    cx: &LateContext<'tcx>,
    pat: &'tcx Pat<'_>,
    arg: &'tcx Expr<'_>,
    body: &'tcx Expr<'_>,
    span: Span,
) {
    if let ExprKind::Block(block, _) = body.kind {
        // Ensure the `if let` statement is the only expression or statement in the for-loop
        let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() {
            let match_stmt = &block.stmts[0];
            if let StmtKind::Semi(inner_expr) = match_stmt.kind {
                Some(inner_expr)
            } else {
                None
            }
        } else if block.stmts.is_empty() {
            block.expr
        } else {
            None
        };

        if_chain! {
            if let Some(inner_expr) = inner_expr;
            if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
                = higher::IfLet::hir(cx, inner_expr);
            // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
            if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
            if path_to_local_id(let_expr, pat_hir_id);
            // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
            if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
            let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
            let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
            if some_ctor || ok_ctor;
            // Ensure epxr in `if let` is not used afterwards
            if !is_local_used(cx, if_then, pat_hir_id);
            then {
                let if_let_type = if some_ctor { "Some" } else { "Ok" };
                // Prepare the error message
                let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);

                // Prepare the help message
                let mut applicability = Applicability::MaybeIncorrect;
                let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
                let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
                    ty::Ref(_, inner, _) => match inner.kind() {
                        ty::Ref(..) => ".copied()",
                        _ => ""
                    }
                    _ => ""
                };

                span_lint_and_then(
                    cx,
                    MANUAL_FLATTEN,
                    span,
                    &msg,
                    |diag| {
                        let sugg = format!("{}{}.flatten()", arg_snippet, copied);
                        diag.span_suggestion(
                            arg.span,
                            "try",
                            sugg,
                            Applicability::MaybeIncorrect,
                        );
                        diag.span_help(
                            inner_expr.span,
                            "...and remove the `if let` statement in the for loop",
                        );
                    }
                );
            }
        }
    }
}

check