use super::{Module, Primitive, Type};

#[derive(Debug, Clone)]
pub struct OptionInfo {
    pub inner: Type,
    pub ffi_type: String,
    pub is_vec: bool,
}

impl OptionInfo {
    pub fn from_type(inner: &Type) -> Self {
        let is_vec = inner.is_vec();
        let ffi_type = Self::compute_ffi_type(inner);

        Self {
            inner: inner.clone(),
            ffi_type,
            is_vec,
        }
    }

    fn compute_ffi_type(inner: &Type) -> String {
        match inner {
            Type::Primitive(p) => format!("FfiOption_{}", p.rust_name()),
            Type::String => "FfiOption_FfiString".to_string(),
            Type::Record(name) | Type::Enum(name) => format!("FfiOption_{}", name),
            Type::Vec(_) => String::new(),
            _ => String::new(),
        }
    }

    pub fn inner_primitive(&self) -> Option<Primitive> {
        self.effective_inner().primitive()
    }

    pub fn is_primitive(&self) -> bool {
        self.effective_inner().is_primitive()
    }

    pub fn is_string(&self) -> bool {
        self.effective_inner().is_string()
    }

    pub fn is_record(&self) -> bool {
        self.effective_inner().is_record()
    }

    pub fn is_enum(&self) -> bool {
        self.effective_inner().is_enum()
    }

    pub fn effective_inner(&self) -> &Type {
        if self.is_vec {
            self.inner.vec_inner().unwrap_or(&self.inner)
        } else {
            &self.inner
        }
    }

    pub fn inner_name(&self) -> Option<&str> {
        self.effective_inner().named_type()
    }

    pub fn struct_size(&self, module: &Module) -> usize {
        self.inner_name()
            .map(|name| module.struct_size(name))
            .unwrap_or(0)
    }

    pub fn is_data_enum(&self, module: &Module) -> bool {
        self.inner_name()
            .map(|name| module.is_data_enum(name))
            .unwrap_or(false)
    }
}
