use crate::landlock::{
    compat::TryCompat, uapi, Access, AddRuleError, AddRulesError, HandleAccessError,
    HandleAccessesError, PrivateAccess, Ruleset, TailoredCompatLevel, ABI,
};

crate::landlock::access::bitflags_type! {
    /// Scope flags
    ///
    /// Each variant of `ScopeFlag` is an [scope
    /// flag](https://docs.kernel.org/userspace-api/landlock.html#scope-flags).
    ///
    /// # Example
    ///
    /// ```
    /// use syd::landlock::{ABI, Access, ScopeFlag};
    ///
    /// let scope_set = ScopeFlag::AbstractUnixSocket | ScopeFlag::Signal;
    ///
    /// let scope_v6 = ScopeFlag::from_all(ABI::V6);
    ///
    /// assert_eq!(scope_set, scope_v6);
    /// ```
    pub struct ScopeFlag: u64 {
        /// Restrict a sandboxed process from connecting to an abstract UNIX socket created by a
        /// process outside the related Landlock domain
        const AbstractUnixSocket = uapi::LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET as u64;
        /// Restrict a sandboxed process from sending a signal to another process outside the
        /// domain.
        const Signal = uapi::LANDLOCK_SCOPE_SIGNAL as u64;
    }
}

impl TailoredCompatLevel for ScopeFlag {}

/// # Warning
///
/// If `ABI <= ABI::V5`, `ScopeFlag::from_all()` returns an empty `ScopeFlag`, which
/// makes `Ruleset::handle_access(ScopeFlag::from_all(ABI::V5))` return an error.
impl Access for ScopeFlag {
    fn from_all(abi: ABI) -> Self {
        match abi {
            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => ScopeFlag::EMPTY,
            ABI::V6 => ScopeFlag::AbstractUnixSocket | ScopeFlag::Signal,
        }
    }
}

impl PrivateAccess for ScopeFlag {
    fn is_empty(self) -> bool {
        ScopeFlag::is_empty(&self)
    }

    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        scoped: Self,
    ) -> Result<(), HandleAccessesError> {
        // We need to record the requested scopes for PrivateRule::check_consistency().
        ruleset.requested_scoped |= scoped;
        ruleset.actual_scoped |= match scoped
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(HandleAccessError::Compat)?
        {
            Some(a) => a,
            None => return Ok(()),
        };
        Ok(())
    }

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
        AddRulesError::Scoped(error)
    }

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
        HandleAccessesError::Scoped(error)
    }
}
