use crate::{
    error::Error,
    interpreter::{
        consts::{Consts, HandledConst},
        namespace::{
            Namespace,
            NamespacedKind,
            Namespaces,
            ProgressKind,
            ScopedProgressKind,
            Typing,
        },
        pointer::Pointer,
        supported_type::{ArrayLen, SliceLen, SupportedType},
        Interpreter,
    },
};
use quote::ToTokens;

impl<'ast> Interpreter<'ast> {
    fn type_from_syn_type_impl(
        &mut self,
        syn_type: &'ast syn::Type,
        syn_attributes: Option<&'ast [syn::Attribute]>,
    ) -> Result<SupportedType, Error> {
        match syn_type {
            syn::Type::Array(syn_ty) => {
                let ty = self.type_from_syn_type(&syn_ty.elem, syn_attributes)?;
                if let syn::Expr::Path(path_expr) = &syn_ty.len {
                    let const_val = Consts::get(self, &path_expr.path)?.0;
                    if let HandledConst::Generic(i) = const_val {
                        return Ok(SupportedType::Array(Box::new(ty), ArrayLen::ConstVar(i)));
                    }
                }

                let len = self.expr_to_const_usize(&syn_ty.len, &syn_ty.len)?;
                Ok(SupportedType::Array(Box::new(ty), ArrayLen::Computed(len)))
            }
            syn::Type::BareFn(_) => Ok(SupportedType::Fn),
            syn::Type::Group(ty) => self.type_from_syn_type(&ty.elem, syn_attributes),
            syn::Type::ImplTrait(_) => Ok(SupportedType::ImplAny),
            syn::Type::Infer(_) => Ok(SupportedType::ImplAny),
            syn::Type::Paren(ty) => self.type_from_syn_type(&ty.elem, syn_attributes),
            syn::Type::Path(syn_ty) => {
                let last_segment = syn_ty.path.segments.last().unwrap();

                let (ty_and_gen, mut ctx) = match Namespaces::find_in_index(self, &syn_ty.path) {
                    Ok((Namespace::Type(ty_id), ctx)) => (
                        Typing::get_from_index(self, ty_id, Some(&syn_ty.path))?,
                        ctx,
                    ),
                    Ok((Namespace::Scope(_), _)) => {
                        return Err(Error::new_spanned(
                            syn_ty,
                            format!("{} exists but is not a type.", syn_ty.to_token_stream()),
                        ))
                    }
                    Ok((Namespace::Forbidden(s), _)) => {
                        return Err(Error::new_spanned(
                            syn_ty,
                            format!(
                                "{} exists but cannot be accessed: {}.",
                                syn_ty.to_token_stream(),
                                s
                            ),
                        ))
                    }
                    Err(_) => {
                        // It's a type we do not know. Maybe this works ?
                        return Ok(SupportedType::ImplAny);
                    }
                };
                let turbofish =
                    if let syn::PathArguments::AngleBracketed(a) = &last_segment.arguments {
                        Some(a)
                    } else {
                        None
                    };
                let (mut ty, gen) = ty_and_gen.destructure();
                let turbofish = self.evaluate_turbofish(turbofish, gen, last_segment)?;

                self.with_turbofish(turbofish, gen, &mut ctx, last_segment, |interpreter| {
                    interpreter
                        .refine_type(&mut ty, false)
                        .expect("may_fail is equal to false");
                    Ok(())
                })?;

                Ok(ty)
            }
            syn::Type::Reference(ty) => Ok(SupportedType::Ref(Box::new(
                self.type_from_syn_type(ty.elem.as_ref(), syn_attributes)?,
            ))),
            syn::Type::Slice(ty) => {
                let slice_len = self.slice_len_from_syn(syn_attributes)?;
                Ok(SupportedType::Slice(
                    Box::new(self.type_from_syn_type(ty.elem.as_ref(), None)?),
                    slice_len,
                ))
            }
            syn::Type::Tuple(ty) => {
                if ty.elems.is_empty() {
                    return Ok(SupportedType::Unit);
                }
                let v: Result<Vec<_>, _> = ty
                    .elems
                    .iter()
                    .map(|ty| self.type_from_syn_type(ty, syn_attributes))
                    .collect();
                let v = v?;
                Ok(SupportedType::Tuple(v))
            }
            _ => Err(Error::new_spanned(
                syn_type,
                format!("Unsupported type `{}`.", syn_type.to_token_stream()),
            )),
        }
    }
    pub fn type_from_syn_type(
        &mut self,
        syn_type: &'ast syn::Type,
        syn_attributes: Option<&'ast [syn::Attribute]>,
    ) -> Result<SupportedType, Error> {
        let mut ty = self.type_from_syn_type_impl(syn_type, syn_attributes)?;
        self.refine_type(&mut ty, false).expect("may_fail = false");
        Ok(ty)
    }

    fn slice_len_from_syn(
        &mut self,
        attributes: Option<&'ast [syn::Attribute]>,
    ) -> Result<SliceLen, Error> {
        let Some(attributes) = attributes else {
            return Ok(SliceLen::None);
        };
        for attr in attributes {
            let syn::Meta::NameValue(nv) = &attr.meta else {
                continue;
            };
            let Some(attr_ident) = nv.path.get_ident() else {
                continue;
            };
            if attr_ident != "init_len" {
                continue;
            }
            let syn::Expr::Cast(expr) = &nv.value else {
                return Err(Error::new_spanned(&nv.value, "Should be `_ as _`."));
            };
            let ty = self.type_from_syn_type(&expr.ty, None)?;
            let syn::Expr::Path(len_path) = expr.expr.as_ref() else {
                return Err(Error::new_spanned(&nv.value, "Should be `some_path as _`."));
            };
            let Some(len_ident) = len_path.path.get_ident() else {
                return Err(Error::new_spanned(
                    &nv.value,
                    "Should be `some_ident as _`.",
                ));
            };
            return SliceLen::from_ident_and_ty(len_ident, ty);
        }
        Ok(SliceLen::None)
    }
    pub fn inspect_type(&mut self, pointer: Pointer) -> SupportedType {
        self.memory.pointed_type(pointer)
    }
}
