//! Types related to getting and setting a property on a DOM node.

use crate::dom::element::DomElement;

/// An error when attempting to retrieve a property from a DOM node.
#[derive(Debug)]
pub enum DomNodeGetPropertyStringError {
    /// The key was too long.
    KeyTooLong,
    /// The key's bytes were not valid UTF-8.
    KeyNotUtf8,
    /// The property exists, but the value is not a string.
    ValueNotString,
    /// The out buffer was smaller than the length of the input element's value.
    OutBufferBelowMinimumSize,
    /// The out buffer was smaller than the length of the local storage item's value.
    #[allow(missing_docs)]
    OutBufferSmallerThanValue { value_len: usize },
    #[allow(missing_docs)]
    Other { error_code: i32 },
}

/// An error when attempting to retrieve a boolean property from a DOM node.
#[derive(Debug)]
pub enum DomNodeGetPropertyBoolError {
    /// The key was too long.
    KeyTooLong,
    /// The key's bytes were not valid UTF-8.
    KeyNotUtf8,
    /// The property exists, but the value is not a boolean.
    ValueNotBool,
    #[allow(missing_docs)]
    Other { error_code: i32 },
}

impl DomElement {
    /// Get DOM node's property.
    /// The property is expected to have a string value, otherwise an error is returned.
    pub fn get_property_string<'out>(
        &self,
        key: &str,
        out_buffer: &'out mut [u8],
    ) -> Result<&'out str, DomNodeGetPropertyStringError> {
        let returned = unsafe {
            afia_component_sys::dom_node_get_property_string(
                self.component_imports_ptr(),
                self.to_i64(),
                key.as_ptr(),
                key.len(),
                out_buffer.as_mut_ptr(),
                out_buffer.len(),
            )
        };

        match returned {
            length if length >= 0 => {
                let length = length as usize;

                // SAFETY: here we are trusting that the Afia host worked as expected and wrote a
                // utf8 string to the buffer.
                let string = unsafe { std::str::from_utf8_unchecked(&out_buffer[0..length]) };

                Ok(string)
            }
            -1 => Err(DomNodeGetPropertyStringError::KeyTooLong),
            -2 => Err(DomNodeGetPropertyStringError::KeyNotUtf8),
            -3 => Err(DomNodeGetPropertyStringError::ValueNotString),
            -4 => Err(DomNodeGetPropertyStringError::OutBufferBelowMinimumSize),
            -5 => {
                let mut val_len = [0; 4];
                val_len.copy_from_slice(&out_buffer[0..4]);
                let val = i32::from_le_bytes(val_len);
                Err(DomNodeGetPropertyStringError::OutBufferSmallerThanValue {
                    value_len: val as usize,
                })
            }
            unknown_error_code => Err(DomNodeGetPropertyStringError::Other {
                error_code: unknown_error_code,
            }),
        }
    }

    /// Get DOM node's property.
    /// The property is expected to have a bool value, otherwise an error is returned.
    pub fn get_property_bool<'out>(&self, key: &str) -> Result<bool, DomNodeGetPropertyBoolError> {
        let returned = unsafe {
            afia_component_sys::dom_node_get_property_bool(
                self.component_imports_ptr(),
                self.to_i64(),
                key.as_ptr(),
                key.len(),
            )
        };

        match returned {
            0 => Ok(false),
            1 => Ok(true),
            -1 => Err(DomNodeGetPropertyBoolError::KeyTooLong),
            -2 => Err(DomNodeGetPropertyBoolError::KeyNotUtf8),
            -3 => Err(DomNodeGetPropertyBoolError::ValueNotBool),
            unknown_error_code => Err(DomNodeGetPropertyBoolError::Other {
                error_code: unknown_error_code,
            }),
        }
    }

    /// Set DOM node's property to a string value.
    pub fn set_property_string(&self, key: &str, value: &str) {
        unsafe {
            afia_component_sys::dom_node_set_property_string(
                self.component_imports_ptr(),
                self.to_i64(),
                key.as_ptr(),
                key.len(),
                value.as_ptr(),
                value.len(),
            )
        }
    }

    /// Set DOM node's property to a boolean value.
    pub fn set_property_bool(&self, key: &str, value: bool) {
        let value: i32 = if value { 1 } else { 0 };
        unsafe {
            afia_component_sys::dom_node_set_property_bool(
                self.component_imports_ptr(),
                self.to_i64(),
                key.as_ptr(),
                key.len(),
                value,
            );
        }
    }
}
