//! HIR for references to types. Paths in these are not yet resolved. They can
//! be directly created from an ast::TypeRef, without further queries.

use hir_expand::name::Name;
use la_arena::Idx;
use rustc_abi::ExternAbi;
use thin_vec::ThinVec;

use crate::{
    LifetimeParamId, TypeParamId,
    expr_store::{ExpressionStore, path::Path},
    hir::{ExprId, PatId},
};

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Mutability {
    Shared,
    Mut,
}

impl Mutability {
    pub fn from_mutable(mutable: bool) -> Mutability {
        if mutable { Mutability::Mut } else { Mutability::Shared }
    }

    pub fn as_keyword_for_ref(self) -> &'static str {
        match self {
            Mutability::Shared => "",
            Mutability::Mut => "mut ",
        }
    }

    pub fn as_keyword_for_ptr(self) -> &'static str {
        match self {
            Mutability::Shared => "const ",
            Mutability::Mut => "mut ",
        }
    }

    /// Returns `true` if the mutability is [`Mut`].
    ///
    /// [`Mut`]: Mutability::Mut
    #[must_use]
    pub fn is_mut(&self) -> bool {
        matches!(self, Self::Mut)
    }

    /// Returns `true` if the mutability is [`Shared`].
    ///
    /// [`Shared`]: Mutability::Shared
    #[must_use]
    pub fn is_shared(&self) -> bool {
        matches!(self, Self::Shared)
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Rawness {
    RawPtr,
    Ref,
}

impl Rawness {
    pub fn from_raw(is_raw: bool) -> Rawness {
        if is_raw { Rawness::RawPtr } else { Rawness::Ref }
    }

    pub fn is_raw(&self) -> bool {
        matches!(self, Self::RawPtr)
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
/// A `TypeRefId` that is guaranteed to always be `TypeRef::Path`. We use this for things like
/// impl's trait, that are always paths but need to be traced back to source code.
pub struct PathId(TypeRefId);

impl PathId {
    #[inline]
    pub fn from_type_ref_unchecked(type_ref: TypeRefId) -> Self {
        Self(type_ref)
    }

    #[inline]
    pub fn type_ref(self) -> TypeRefId {
        self.0
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct TraitRef {
    pub path: PathId,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct FnType {
    pub binder: Option<Box<[Name]>>,
    pub params: Box<[(Option<Name>, TypeRefId)]>,
    pub is_varargs: bool,
    pub is_unsafe: bool,
    pub abi: ExternAbi,
}

impl FnType {
    #[inline]
    pub fn split_params_and_ret(&self) -> (&[(Option<Name>, TypeRefId)], TypeRefId) {
        let (ret, params) = self.params.split_last().expect("should have at least return type");
        (params, ret.1)
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ArrayType {
    pub ty: TypeRefId,
    pub len: ConstRef,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct RefType {
    pub ty: TypeRefId,
    pub lifetime: Option<LifetimeRefId>,
    pub mutability: Mutability,
}

/// Compare ty::Ty
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TypeRef {
    Never,
    Placeholder,
    Tuple(ThinVec<TypeRefId>),
    Path(Path),
    RawPtr(TypeRefId, Mutability),
    // FIXME: Unbox this once `Idx` has a niche,
    // as `RefType` should shrink by 4 bytes then
    Reference(Box<RefType>),
    Array(ArrayType),
    Slice(TypeRefId),
    /// A fn pointer. Last element of the vector is the return type.
    Fn(Box<FnType>),
    ImplTrait(ThinVec<TypeBound>),
    DynTrait(ThinVec<TypeBound>),
    TypeParam(TypeParamId),
    PatternType(TypeRefId, PatId),
    Error,
}

#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
const _: () = assert!(size_of::<TypeRef>() == 24);

pub type TypeRefId = Idx<TypeRef>;

pub type LifetimeRefId = Idx<LifetimeRef>;

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum LifetimeRef {
    Named(Name),
    Static,
    Placeholder,
    Param(LifetimeParamId),
    Error,
}

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum TypeBound {
    Path(PathId, TraitBoundModifier),
    ForLifetime(ThinVec<Name>, PathId),
    Lifetime(LifetimeRefId),
    Use(ThinVec<UseArgRef>),
    Error,
}

#[cfg(target_pointer_width = "64")]
const _: [(); 16] = [(); size_of::<TypeBound>()];

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum UseArgRef {
    Name(Name),
    Lifetime(LifetimeRefId),
}

/// A modifier on a bound, currently this is only used for `?Sized`, where the
/// modifier is `Maybe`.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum TraitBoundModifier {
    None,
    Maybe,
}

impl TypeRef {
    pub(crate) fn unit() -> TypeRef {
        TypeRef::Tuple(ThinVec::new())
    }
}

impl TypeBound {
    pub fn as_path<'a>(&self, map: &'a ExpressionStore) -> Option<(&'a Path, TraitBoundModifier)> {
        match self {
            &TypeBound::Path(p, m) => Some((&map[p], m)),
            &TypeBound::ForLifetime(_, p) => Some((&map[p], TraitBoundModifier::None)),
            TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => None,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ConstRef {
    pub expr: ExprId,
}
