//! Things related to opaques in the next-trait-solver.

use intern::Interned;
use rustc_ast_ir::try_visit;

use crate::next_solver::SolverDefId;

use super::{CanonicalVarKind, DbInterner, interned_vec_nolifetime_salsa};

pub type OpaqueTypeKey<'db> = rustc_type_ir::OpaqueTypeKey<DbInterner<'db>>;
pub type PredefinedOpaquesData<'db> = rustc_type_ir::solve::PredefinedOpaquesData<DbInterner<'db>>;
pub type ExternalConstraintsData<'db> =
    rustc_type_ir::solve::ExternalConstraintsData<DbInterner<'db>>;

#[salsa::interned(constructor = new_, debug)]
pub struct PredefinedOpaques<'db> {
    #[returns(ref)]
    kind_: rustc_type_ir::solve::PredefinedOpaquesData<DbInterner<'db>>,
}

impl<'db> PredefinedOpaques<'db> {
    pub fn new(interner: DbInterner<'db>, data: PredefinedOpaquesData<'db>) -> Self {
        PredefinedOpaques::new_(interner.db(), data)
    }

    pub fn inner(&self) -> &PredefinedOpaquesData<'db> {
        salsa::with_attached_database(|db| {
            let inner = self.kind_(db);
            // SAFETY: ¯\_(ツ)_/¯
            unsafe { std::mem::transmute(inner) }
        })
        .unwrap()
    }
}

impl<'db> rustc_type_ir::TypeVisitable<DbInterner<'db>> for PredefinedOpaques<'db> {
    fn visit_with<V: rustc_type_ir::TypeVisitor<DbInterner<'db>>>(
        &self,
        visitor: &mut V,
    ) -> V::Result {
        self.opaque_types.visit_with(visitor)
    }
}

impl<'db> rustc_type_ir::TypeFoldable<DbInterner<'db>> for PredefinedOpaques<'db> {
    fn try_fold_with<F: rustc_type_ir::FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(PredefinedOpaques::new(
            folder.cx(),
            PredefinedOpaquesData {
                opaque_types: self
                    .opaque_types
                    .iter()
                    .cloned()
                    .map(|opaque| opaque.try_fold_with(folder))
                    .collect::<Result<_, F::Error>>()?,
            },
        ))
    }
    fn fold_with<F: rustc_type_ir::TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        PredefinedOpaques::new(
            folder.cx(),
            PredefinedOpaquesData {
                opaque_types: self
                    .opaque_types
                    .iter()
                    .cloned()
                    .map(|opaque| opaque.fold_with(folder))
                    .collect(),
            },
        )
    }
}

impl<'db> std::ops::Deref for PredefinedOpaques<'db> {
    type Target = PredefinedOpaquesData<'db>;

    fn deref(&self) -> &Self::Target {
        self.inner()
    }
}

interned_vec_nolifetime_salsa!(SolverDefIds, SolverDefId);

#[salsa::interned(constructor = new_, debug)]
pub struct ExternalConstraints<'db> {
    #[returns(ref)]
    kind_: rustc_type_ir::solve::ExternalConstraintsData<DbInterner<'db>>,
}

impl<'db> ExternalConstraints<'db> {
    pub fn new(interner: DbInterner<'db>, data: ExternalConstraintsData<'db>) -> Self {
        ExternalConstraints::new_(interner.db(), data)
    }

    pub fn inner(&self) -> &ExternalConstraintsData<'db> {
        salsa::with_attached_database(|db| {
            let inner = self.kind_(db);
            // SAFETY: ¯\_(ツ)_/¯
            unsafe { std::mem::transmute(inner) }
        })
        .unwrap()
    }
}

impl<'db> std::ops::Deref for ExternalConstraints<'db> {
    type Target = ExternalConstraintsData<'db>;

    fn deref(&self) -> &Self::Target {
        self.inner()
    }
}

impl<'db> rustc_type_ir::TypeVisitable<DbInterner<'db>> for ExternalConstraints<'db> {
    fn visit_with<V: rustc_type_ir::TypeVisitor<DbInterner<'db>>>(
        &self,
        visitor: &mut V,
    ) -> V::Result {
        try_visit!(self.region_constraints.visit_with(visitor));
        try_visit!(self.opaque_types.visit_with(visitor));
        self.normalization_nested_goals.visit_with(visitor)
    }
}

impl<'db> rustc_type_ir::TypeFoldable<DbInterner<'db>> for ExternalConstraints<'db> {
    fn try_fold_with<F: rustc_type_ir::FallibleTypeFolder<DbInterner<'db>>>(
        self,
        folder: &mut F,
    ) -> Result<Self, F::Error> {
        Ok(ExternalConstraints::new(
            folder.cx(),
            ExternalConstraintsData {
                region_constraints: self.region_constraints.clone().try_fold_with(folder)?,
                opaque_types: self
                    .opaque_types
                    .iter()
                    .cloned()
                    .map(|opaque| opaque.try_fold_with(folder))
                    .collect::<Result<_, F::Error>>()?,
                normalization_nested_goals: self
                    .normalization_nested_goals
                    .clone()
                    .try_fold_with(folder)?,
            },
        ))
    }
    fn fold_with<F: rustc_type_ir::TypeFolder<DbInterner<'db>>>(self, folder: &mut F) -> Self {
        ExternalConstraints::new(
            folder.cx(),
            ExternalConstraintsData {
                region_constraints: self.region_constraints.clone().fold_with(folder),
                opaque_types: self
                    .opaque_types
                    .iter()
                    .cloned()
                    .map(|opaque| opaque.fold_with(folder))
                    .collect(),
                normalization_nested_goals: self
                    .normalization_nested_goals
                    .clone()
                    .fold_with(folder),
            },
        )
    }
}
