use std::fmt;

use derive_where::derive_where;
#[cfg(feature = "nightly")]
use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher};
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash, StableHash_NoContext};
use rustc_type_ir_macros::{
    GenericTypeVisitable, Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic,
};

use crate::{self as ty, BoundVarIndexKind, Interner};

/// Represents a constant in Rust.
#[derive_where(Clone, Copy, Hash, PartialEq; I: Interner)]
#[derive(GenericTypeVisitable)]
#[cfg_attr(
    feature = "nightly",
    derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext)
)]
pub enum ConstKind<I: Interner> {
    /// A const generic parameter.
    Param(I::ParamConst),

    /// Infer the value of the const.
    Infer(InferConst),

    /// Bound const variable, used only when preparing a trait query.
    Bound(BoundVarIndexKind, ty::BoundConst<I>),

    /// A placeholder const - universally quantified higher-ranked const.
    Placeholder(ty::PlaceholderConst<I>),

    /// An unnormalized const item such as an anon const or assoc const or free const item.
    /// Right now anything other than anon consts does not actually work properly but this
    /// should
    Unevaluated(ty::UnevaluatedConst<I>),

    /// Used to hold computed value.
    Value(I::ValueConst),

    /// A placeholder for a const which could not be computed; this is
    /// propagated to avoid useless error messages.
    Error(I::ErrorGuaranteed),

    /// Unevaluated non-const-item, used by `feature(generic_const_exprs)` to represent
    /// const arguments such as `N + 1` or `foo(N)`
    Expr(I::ExprConst),
}

impl<I: Interner> Eq for ConstKind<I> {}

impl<I: Interner> fmt::Debug for ConstKind<I> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use ConstKind::*;

        match self {
            Param(param) => write!(f, "{param:?}"),
            Infer(var) => write!(f, "{var:?}"),
            Bound(debruijn, var) => crate::debug_bound_var(f, *debruijn, var),
            Placeholder(placeholder) => write!(f, "{placeholder:?}"),
            Unevaluated(uv) => write!(f, "{uv:?}"),
            Value(val) => write!(f, "{val:?}"),
            Error(_) => write!(f, "{{const error}}"),
            Expr(expr) => write!(f, "{expr:?}"),
        }
    }
}

/// An unevaluated (potentially generic) constant used in the type-system.
#[derive_where(Clone, Copy, Debug, Hash, PartialEq; I: Interner)]
#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)]
#[cfg_attr(
    feature = "nightly",
    derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext)
)]
pub struct UnevaluatedConst<I: Interner> {
    #[type_foldable(identity)]
    #[type_visitable(ignore)]
    pub kind: UnevaluatedConstKind<I>,
    pub args: I::GenericArgs,

    /// This field exists to prevent the creation of `UnevaluatedConst` without using [`UnevaluatedConst::new`].
    #[derive_where(skip(Debug))]
    pub(crate) _use_unevaluated_const_new_instead: (),
}

impl<I: Interner> Eq for UnevaluatedConst<I> {}

impl<I: Interner> UnevaluatedConst<I> {
    #[inline]
    pub fn new(
        interner: I,
        kind: UnevaluatedConstKind<I>,
        args: I::GenericArgs,
    ) -> UnevaluatedConst<I> {
        if cfg!(debug_assertions) {
            let def_id = match kind {
                ty::UnevaluatedConstKind::Projection { def_id } => def_id.into(),
                ty::UnevaluatedConstKind::Inherent { def_id } => def_id.into(),
                ty::UnevaluatedConstKind::Free { def_id } => def_id.into(),
                ty::UnevaluatedConstKind::Anon { def_id } => def_id.into(),
            };
            interner.debug_assert_args_compatible(def_id, args);
        }
        UnevaluatedConst { kind, args, _use_unevaluated_const_new_instead: () }
    }

    pub fn type_of(self, interner: I) -> ty::Unnormalized<I, I::Ty> {
        let def_id = match self.kind {
            ty::UnevaluatedConstKind::Projection { def_id } => def_id.into(),
            ty::UnevaluatedConstKind::Inherent { def_id } => def_id.into(),
            ty::UnevaluatedConstKind::Free { def_id } => def_id.into(),
            ty::UnevaluatedConstKind::Anon { def_id } => def_id.into(),
        };
        interner.type_of(def_id).instantiate(interner, self.args)
    }
}

/// UnevaluatedConstKind is extremely similar to AliasTyKind, and likely should be reasoned about
/// and handled in very similar ways. The documentation for AliasTyKind/etc. may be helpful when
/// learning about UnevaluatedConstKind.
#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)]
#[derive(GenericTypeVisitable, Lift_Generic)]
#[cfg_attr(
    feature = "nightly",
    derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext)
)]
pub enum UnevaluatedConstKind<I: Interner> {
    /// A projection `<Type as Trait>::AssocConst`
    Projection { def_id: I::TraitAssocConstId },
    /// An associated constant in an inherent `impl`
    Inherent { def_id: I::InherentAssocConstId },
    /// A free constant, outside an impl block.
    Free { def_id: I::FreeConstAliasId },
    /// Anonymous constant, e.g. the `1 + 2` in `[u8; 1 + 2]`.
    Anon { def_id: I::UnevaluatedConstId },
}

impl<I: Interner> UnevaluatedConstKind<I> {
    pub fn new_from_def_id(interner: I, def_id: I::DefId) -> Self {
        interner.unevaluated_const_kind_from_def_id(def_id)
    }

    pub fn is_type_const(self, interner: I) -> bool {
        match self {
            UnevaluatedConstKind::Projection { def_id } => interner.is_type_const(def_id.into()),
            UnevaluatedConstKind::Inherent { def_id } => interner.is_type_const(def_id.into()),
            UnevaluatedConstKind::Free { def_id } => interner.is_type_const(def_id.into()),
            UnevaluatedConstKind::Anon { def_id } => interner.is_type_const(def_id.into()),
        }
    }

    pub fn def_span(self, interner: I) -> I::Span {
        match self {
            UnevaluatedConstKind::Projection { def_id } => interner.def_span(def_id.into()),
            UnevaluatedConstKind::Inherent { def_id } => interner.def_span(def_id.into()),
            UnevaluatedConstKind::Free { def_id } => interner.def_span(def_id.into()),
            UnevaluatedConstKind::Anon { def_id } => interner.def_span(def_id.into()),
        }
    }

    pub fn opt_def_id(self) -> Option<I::DefId> {
        match self {
            UnevaluatedConstKind::Projection { def_id } => Some(def_id.into()),
            UnevaluatedConstKind::Inherent { def_id } => Some(def_id.into()),
            UnevaluatedConstKind::Free { def_id } => Some(def_id.into()),
            UnevaluatedConstKind::Anon { def_id } => Some(def_id.into()),
        }
    }
}

rustc_index::newtype_index! {
    /// A **`const`** **v**ariable **ID**.
    #[encodable]
    #[orderable]
    #[debug_format = "?{}c"]
    #[gate_rustc_only]
    pub struct ConstVid {}
}

/// An inference variable for a const, for use in const generics.
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext))]
pub enum InferConst {
    /// Infer the value of the const.
    Var(ConstVid),
    /// A fresh const variable. See `TypeFreshener` for more details.
    Fresh(u32),
}

impl fmt::Debug for InferConst {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InferConst::Var(var) => write!(f, "{var:?}"),
            InferConst::Fresh(var) => write!(f, "Fresh({var:?})"),
        }
    }
}

#[cfg(feature = "nightly")]
impl StableHash for InferConst {
    fn stable_hash<Hcx: StableHashCtxt>(&self, hcx: &mut Hcx, hasher: &mut StableHasher) {
        match self {
            InferConst::Var(_) => {
                panic!("const variables should not be hashed: {self:?}")
            }
            InferConst::Fresh(i) => i.stable_hash(hcx, hasher),
        }
    }
}

/// This datastructure is used to represent the value of constants used in the type system.
///
/// We explicitly choose a different datastructure from the way values are processed within
/// CTFE, as in the type system equal values (according to their `PartialEq`) must also have
/// equal representation (`==` on the rustc data structure, e.g. `ValTree`) and vice versa.
/// Since CTFE uses `AllocId` to represent pointers, it often happens that two different
/// `AllocId`s point to equal values. So we may end up with different representations for
/// two constants whose value is `&42`. Furthermore any kind of struct that has padding will
/// have arbitrary values within that padding, even if the values of the struct are the same.
///
/// `ValTree` does not have this problem with representation, as it only contains integers or
/// lists of (nested) `ty::Const`s (which may indirectly contain more `ValTree`s).
#[derive_where(Clone, Copy, Debug, Hash, Eq, PartialEq; I: Interner)]
#[derive(TypeVisitable_Generic, TypeFoldable_Generic, GenericTypeVisitable)]
#[cfg_attr(
    feature = "nightly",
    derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext)
)]
pub enum ValTreeKind<I: Interner> {
    /// integers, `bool`, `char` are represented as scalars.
    /// See the `ScalarInt` documentation for how `ScalarInt` guarantees that equal values
    /// of these types have the same representation.
    Leaf(I::ScalarInt),

    /// The fields of any kind of aggregate. Structs, tuples and arrays are represented by
    /// listing their fields' values in order.
    ///
    /// Enums are represented by storing their variant index as a u32 field, followed by all
    /// the fields of the variant.
    ///
    /// ZST types are represented as an empty slice.
    Branch(I::Consts),
}

impl<I: Interner> ValTreeKind<I> {
    /// Converts to a `ValTreeKind::Leaf` value, `panic`'ing
    /// if this valtree is some other kind.
    #[inline]
    pub fn to_leaf(&self) -> I::ScalarInt {
        match self {
            ValTreeKind::Leaf(s) => *s,
            ValTreeKind::Branch(..) => panic!("expected leaf, got {:?}", self),
        }
    }

    /// Converts to a `ValTreeKind::Branch` value, `panic`'ing
    /// if this valtree is some other kind.
    #[inline]
    pub fn to_branch(&self) -> I::Consts {
        match self {
            ValTreeKind::Branch(branch) => *branch,
            ValTreeKind::Leaf(..) => panic!("expected branch, got {:?}", self),
        }
    }

    /// Attempts to convert to a `ValTreeKind::Leaf` value.
    pub fn try_to_leaf(&self) -> Option<I::ScalarInt> {
        match self {
            ValTreeKind::Leaf(s) => Some(*s),
            ValTreeKind::Branch(_) => None,
        }
    }

    /// Attempts to convert to a `ValTreeKind::Branch` value.
    pub fn try_to_branch(&self) -> Option<I::Consts> {
        match self {
            ValTreeKind::Branch(branch) => Some(*branch),
            ValTreeKind::Leaf(_) => None,
        }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash))]
pub enum AnonConstKind {
    /// `feature(generic_const_exprs)` anon consts are allowed to use arbitrary generic parameters in scope
    GCE,
    /// stable `min_const_generics` anon consts are not allowed to use any generic parameters
    MCG,
    /// `feature(generic_const_args)` anon consts are allowed to use arbitrary
    /// generic parameters in scope, but only if they syntactically reference them.
    GCA,
    /// anon consts used as the length of a repeat expr are syntactically allowed to use generic parameters
    /// but must not depend on the actual instantiation. See #76200 for more information
    RepeatExprCount,
    /// anon consts outside of the type system, e.g. enum discriminants
    NonTypeSystem,
}
