use crate::BuiltinFunctionIndex;
#[cfg(feature = "component-model")]
use crate::component::ComponentBuiltinFunctionIndex;

/// Enumeration of all possible ways that wasm may execute code in the
/// host.
///
/// This type is intended to be serialized into a 32-bit index (or smaller) and
/// is used by Pulley for example to identify how transitions from the guest to
/// the host are performed.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(test, derive(arbitrary::Arbitrary))]
pub enum HostCall {
    /// An "array call" is being done which means that the wasm is calling the
    /// host using the array calling convention (e.g. `VMArrayCallNative`).
    ArrayCall,

    /// A builtin function, e.g. for `memory.grow`, is being called. Each
    /// builtin has its own ABI.
    Builtin(BuiltinFunctionIndex),

    /// A lowered component function is being called. This is done by a
    /// trampoline generated by Cranelift and is distinct from the array calling
    /// convention.
    ///
    /// This correspond to `VMLoweringCallee`.
    #[cfg(feature = "component-model")]
    ComponentLowerImport,

    /// A builtin function, but specifically for components. For example string
    /// transcoders.
    #[cfg(feature = "component-model")]
    ComponentBuiltin(ComponentBuiltinFunctionIndex),
}

impl HostCall {
    /// Returns a 32-bit index for this hostcall.
    pub const fn index(&self) -> u32 {
        match self {
            HostCall::ArrayCall => 0,
            HostCall::Builtin(i) => 1 + i.index(),
            #[cfg(feature = "component-model")]
            HostCall::ComponentLowerImport => 1 + BuiltinFunctionIndex::len(),
            #[cfg(feature = "component-model")]
            HostCall::ComponentBuiltin(i) => 2 + BuiltinFunctionIndex::len() + i.index(),
        }
    }

    /// Create a `HostCall` from the result of an earlier call to
    /// `HostCall::index`.
    pub fn from_index(index: u32) -> Self {
        let host_call = match index {
            0 => Self::ArrayCall,
            _ if index < 1 + BuiltinFunctionIndex::len() => {
                Self::Builtin(BuiltinFunctionIndex::from_u32(index - 1))
            }
            #[cfg(feature = "component-model")]
            _ if index == 1 + BuiltinFunctionIndex::len() => Self::ComponentLowerImport,
            #[cfg(feature = "component-model")]
            _ if index < 2 + BuiltinFunctionIndex::len() + ComponentBuiltinFunctionIndex::len() => {
                Self::ComponentBuiltin(ComponentBuiltinFunctionIndex::from_u32(
                    index - 2 - BuiltinFunctionIndex::len(),
                ))
            }
            _ => panic!("bad host call index: {index}"),
        };
        debug_assert_eq!(index, host_call.index());
        host_call
    }
}

impl From<BuiltinFunctionIndex> for HostCall {
    fn from(idx: BuiltinFunctionIndex) -> HostCall {
        HostCall::Builtin(idx)
    }
}

#[cfg(feature = "component-model")]
impl From<ComponentBuiltinFunctionIndex> for HostCall {
    fn from(idx: ComponentBuiltinFunctionIndex) -> HostCall {
        HostCall::ComponentBuiltin(idx)
    }
}
