use rustc_type_ir::{AliasRelationDirection, inherent::Term as _};

use crate::next_solver::{
    Const, PredicateKind, Term, Ty,
    fulfill::{FulfillmentCtxt, NextSolverError},
    infer::{at::At, traits::Obligation},
};

impl<'db> At<'_, 'db> {
    pub(crate) fn structurally_normalize_ty(
        &self,
        ty: Ty<'db>,
        fulfill_cx: &mut FulfillmentCtxt<'db>,
    ) -> Result<Ty<'db>, Vec<NextSolverError<'db>>> {
        self.structurally_normalize_term(ty.into(), fulfill_cx).map(|term| term.expect_type())
    }

    pub(crate) fn structurally_normalize_const(
        &self,
        ct: Const<'db>,
        fulfill_cx: &mut FulfillmentCtxt<'db>,
    ) -> Result<Const<'db>, Vec<NextSolverError<'db>>> {
        self.structurally_normalize_term(ct.into(), fulfill_cx).map(|term| term.expect_const())
    }

    pub(crate) fn structurally_normalize_term(
        &self,
        term: Term<'db>,
        fulfill_cx: &mut FulfillmentCtxt<'db>,
    ) -> Result<Term<'db>, Vec<NextSolverError<'db>>> {
        assert!(!term.is_infer(), "should have resolved vars before calling");

        if term.to_alias_term(self.infcx.interner).is_none() {
            return Ok(term);
        }

        let new_infer = self.infcx.next_term_var_of_kind(term, self.cause.span());

        // We simply emit an `alias-eq` goal here, since that will take care of
        // normalizing the LHS of the projection until it is a rigid projection
        // (or a not-yet-defined opaque in scope).
        let obligation = Obligation::new(
            self.infcx.interner,
            *self.cause,
            self.param_env,
            PredicateKind::AliasRelate(term, new_infer, AliasRelationDirection::Equate),
        );

        fulfill_cx.register_predicate_obligation(self.infcx, obligation);
        let errors = fulfill_cx.try_evaluate_obligations(self.infcx);
        if !errors.is_empty() {
            return Err(errors);
        }

        Ok(self.infcx.resolve_vars_if_possible(new_infer))
    }
}
