use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes};
use clippy_utils::source::SpanRangeExt;
use itertools::Itertools;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind};
use rustc_lint::LateContext;
use rustc_span::sym;

use super::STRING_LIT_CHARS_ANY;

pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx Expr<'tcx>,
    recv: &Expr<'_>,
    param: &'tcx Param<'tcx>,
    body: &Expr<'_>,
    msrv: Msrv,
) {
    if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator)
        && let PatKind::Binding(_, arg, _, _) = param.pat.kind
        && let ExprKind::Lit(lit_kind) = recv.kind
        && let LitKind::Str(val, _) = lit_kind.node
        && let ExprKind::Binary(kind, lhs, rhs) = body.kind
        && let BinOpKind::Eq = kind.node
        && let Some(lhs_path) = lhs.res_local_id()
        && let Some(rhs_path) = rhs.res_local_id()
        && let scrutinee = match (lhs_path == arg, rhs_path == arg) {
            (true, false) => rhs,
            (false, true) => lhs,
            _ => return,
        }
        && msrv.meets(cx, msrvs::MATCHES_MACRO)
        && !is_from_proc_macro(cx, expr)
        && let Some(scrutinee_snip) = scrutinee.span.get_source_text(cx)
    {
        // Normalize the char using `map` so `join` doesn't use `Display`, if we don't then
        // something like `r"\"` will become `'\'`, which is of course invalid
        let pat_snip = val.as_str().chars().map(|c| format!("{c:?}")).join(" | ");

        span_lint_and_then(
            cx,
            STRING_LIT_CHARS_ANY,
            expr.span,
            "usage of `.chars().any(...)` to check if a char matches any from a string literal",
            |diag| {
                diag.span_suggestion_verbose(
                    expr.span,
                    "use `matches!(...)` instead",
                    format!("matches!({scrutinee_snip}, {pat_snip})"),
                    Applicability::MachineApplicable,
                );
            },
        );
    }
}
