use rustc_hir as hir;
use rustc_hir_pretty::qpath_to_string;
use rustc_lint_defs::builtin::STATIC_MUT_REFS;
use rustc_middle::ty::{Mutability, TyCtxt};
use rustc_span::Span;

use crate::errors;

/// Check for shared or mutable references of `static mut` inside expression
pub fn maybe_expr_static_mut(tcx: TyCtxt<'_>, expr: hir::Expr<'_>) {
    let span = expr.span;
    let hir_id = expr.hir_id;
    if let hir::ExprKind::AddrOf(borrow_kind, m, expr) = expr.kind
        && matches!(borrow_kind, hir::BorrowKind::Ref)
        && let Some(var) = path_if_static_mut(tcx, expr)
    {
        handle_static_mut_ref(tcx, span, var, span.edition().at_least_rust_2024(), m, hir_id);
    }
}

/// Check for shared or mutable references of `static mut` inside statement
pub fn maybe_stmt_static_mut(tcx: TyCtxt<'_>, stmt: hir::Stmt<'_>) {
    if let hir::StmtKind::Let(loc) = stmt.kind
        && let hir::PatKind::Binding(ba, _, _, _) = loc.pat.kind
        && let hir::ByRef::Yes(rmutbl) = ba.0
        && let Some(init) = loc.init
        && let Some(var) = path_if_static_mut(tcx, init)
    {
        handle_static_mut_ref(
            tcx,
            init.span,
            var,
            loc.span.edition().at_least_rust_2024(),
            rmutbl,
            stmt.hir_id,
        );
    }
}

fn path_if_static_mut(tcx: TyCtxt<'_>, expr: &hir::Expr<'_>) -> Option<String> {
    if let hir::ExprKind::Path(qpath) = expr.kind
        && let hir::QPath::Resolved(_, path) = qpath
        && let hir::def::Res::Def(def_kind, _) = path.res
        && let hir::def::DefKind::Static { mutability: Mutability::Mut, nested: false } = def_kind
    {
        return Some(qpath_to_string(&tcx, &qpath));
    }
    None
}

fn handle_static_mut_ref(
    tcx: TyCtxt<'_>,
    span: Span,
    var: String,
    e2024: bool,
    mutable: Mutability,
    hir_id: hir::HirId,
) {
    if e2024 {
        let (sugg, shared) = if mutable == Mutability::Mut {
            (errors::StaticMutRefSugg::Mut { span, var }, "mutable")
        } else {
            (errors::StaticMutRefSugg::Shared { span, var }, "shared")
        };
        tcx.sess.psess.dcx.emit_err(errors::StaticMutRef { span, sugg, shared });
    } else {
        let (sugg, shared) = if mutable == Mutability::Mut {
            (errors::RefOfMutStaticSugg::Mut { span, var }, "mutable")
        } else {
            (errors::RefOfMutStaticSugg::Shared { span, var }, "shared")
        };
        tcx.emit_node_span_lint(
            STATIC_MUT_REFS,
            hir_id,
            span,
            errors::RefOfMutStatic { span, sugg, shared },
        );
    }
}
