use crate::{
    error::{Error, HasSpan},
    interpreter::{
        data::{ArcisValue, Data, FieldId},
        interpreter_conversion::ConversionKind,
        pointer::Pointer,
        supported_type::SupportedType,
        Interpreter,
    },
};

impl<'ast> Interpreter<'ast> {
    pub fn apply_pat_type(
        &mut self,
        pat: &'ast syn::PatType,
        pointer: Pointer,
        no_move: bool,
    ) -> Result<(), Error> {
        let new_type = self.type_from_syn_type(&pat.ty, None)?;
        let converted_pointer =
            self.conversion(pointer, &new_type, ConversionKind::Implicit, &pat.ty)?;
        self.apply_pat(&pat.pat, converted_pointer, no_move)
    }
    pub fn apply_pat(
        &mut self,
        pat: &'ast syn::Pat,
        pointer: Pointer,
        mut no_move: bool,
    ) -> Result<(), Error> {
        match pat {
            syn::Pat::Ident(i) => {
                if let Some(sub_pat) = &i.subpat {
                    return Err(Error::new_spanned(sub_pat.0, "Unsupported pattern type."));
                }
                if !self.has_const(&i.ident) {
                    if (no_move && i.mutability.is_none()) || i.by_ref.is_some() {
                        self.new_binding_from_data(&i.ident, Data::Pointer(pointer));
                    } else {
                        self.new_binding_from_pointer(&i.ident, pointer)?;
                    }
                }
            }
            syn::Pat::Paren(pat) => {
                self.apply_pat(&pat.pat, pointer, no_move)?;
            }
            syn::Pat::Reference(pat) => {
                let data = self.memory.compute(pointer)?;
                let Data::Pointer(new_pointer) = data else {
                    return Err(Error::new_spanned(
                        pat,
                        "Using reference pattern on a non-reference.",
                    ));
                };
                self.apply_pat(&pat.pat, new_pointer, false)?;
            }
            syn::Pat::Struct(pat) => {
                let pointed_type = self.memory.pointed_type(pointer);
                if pointed_type.is_reference() {
                    no_move = true;
                }
                let underlying_type = pointed_type.underlying_type();
                let SupportedType::Struct(s) = underlying_type else {
                    return Err(Error::new_spanned(
                        pat,
                        format!(
                            "Using struct pattern on a non-struct type: {}",
                            underlying_type
                        ),
                    ));
                };
                for field_pat in &pat.fields {
                    let field_id = match &field_pat.member {
                        syn::Member::Named(name) => {
                            let Some(field_id) = s.get_field_id(name) else {
                                return Err(Error::new_spanned(
                                    name,
                                    format!("Unknown Field for struct: {underlying_type}"),
                                ));
                            };
                            *field_id
                        }
                        syn::Member::Unnamed(s) => s.index as FieldId,
                    };
                    let field_pointer = self.memory.create_index(
                        pointer,
                        ArcisValue::from(field_id),
                        field_pat.span(),
                    )?;
                    self.apply_pat(&field_pat.pat, field_pointer, no_move)?
                }
            }
            syn::Pat::Slice(syn::PatSlice { elems, .. })
            | syn::Pat::Tuple(syn::PatTuple { elems, .. })
            | syn::Pat::TupleStruct(syn::PatTupleStruct { elems, .. }) => {
                if self.memory.pointed_type(pointer).is_reference() {
                    no_move = true;
                }
                for (index, elem) in elems.iter().enumerate() {
                    let field_pointer =
                        self.memory
                            .create_index(pointer, ArcisValue::from(index), elem.span())?;
                    self.apply_pat(elem, field_pointer, no_move)?
                }
            }
            syn::Pat::Type(pat) => {
                self.apply_pat_type(pat, pointer, no_move)?;
            }
            syn::Pat::Wild(_) => {
                // nothing to do
            }
            _ => return Err(Error::new_spanned(pat, "unsupported pattern")),
        }
        Ok(())
    }
}
