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

#[derive(Debug, Clone, PartialEq)]
pub enum GenericValue {
    Const(HandledConst),
    Type(SupportedType),
}

impl<'ast> Interpreter<'ast> {
    pub fn evaluate_turbofish(
        &mut self,
        turbofish: Option<&'ast syn::AngleBracketedGenericArguments>,
        generics: Option<&'ast syn::Generics>,
        expr: impl ToTokens,
    ) -> Result<Option<Vec<GenericValue>>, Error> {
        let res = if let Some(turbofish) = turbofish {
            let Some(generics) = generics else {
                return Err(Error::new_spanned(
                    turbofish,
                    format!(
                        "Turbofish `{}` provided, but `{}` cannot have generics.",
                        turbofish.to_token_stream(),
                        expr.to_token_stream(),
                    ),
                ));
            };
            let mut param_iter = generics
                .params
                .iter()
                .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_)));
            let mut vals: Vec<GenericValue> = Vec::new();
            for x in turbofish
                .args
                .iter()
                .filter(|arg| !matches!(arg, syn::GenericArgument::Lifetime(_)))
            {
                let Some(param) = param_iter.next() else {
                    return Err(Error::new_spanned(
                        expr,
                        format!("Too many generic arguments in {}.", x.to_token_stream()),
                    ));
                };
                match param {
                    syn::GenericParam::Type(_) => {
                        let syn::GenericArgument::Type(ty) = x else {
                            return Err(Error::new_spanned(
                                x,
                                format!(
                                    " `{}` provided, but was expecting a type.",
                                    x.to_token_stream(),
                                ),
                            ));
                        };
                        let res = self.type_from_syn_type(ty, None)?;
                        vals.push(GenericValue::Type(res));
                    }
                    syn::GenericParam::Const(_) => {
                        let res = match x {
                            syn::GenericArgument::Type(syn::Type::Path(ty)) => {
                                Consts::get(self, &ty.path)?.0
                            }
                            syn::GenericArgument::Const(expr) => {
                                let ptr = self.run_expr(expr)?;
                                let addr = self.memory.clone_and_push(ptr)?;
                                HandledConst::Computed(addr)
                            }
                            _ => {
                                return Err(Error::new_spanned(
                                    x,
                                    format!("expected a const, received {}.", x.to_token_stream()),
                                ))
                            }
                        };
                        vals.push(GenericValue::Const(res));
                    }
                    syn::GenericParam::Lifetime(_) => {
                        unreachable!("We checked this was not possible.")
                    }
                }
            }
            for ty in &mut vals {
                if let GenericValue::Type(ty) = ty {
                    self.refine_type(ty, false)
                        .map_err(|e| Error::new_spanned(turbofish, e))?;
                }
            }
            Some(vals)
        } else {
            None
        };
        Ok(res)
    }
    pub fn with_turbofish<T>(
        &mut self,
        turbofish: Option<Vec<GenericValue>>,
        generics: Option<&'ast syn::Generics>,
        ctx: &mut GenericContext,
        expr: impl ToTokens,
        f: impl FnOnce(&mut Interpreter<'ast>) -> Result<T, Error>,
    ) -> Result<T, Error> {
        self.with_generic_context(ctx, |interpreter| {
            if let Some(turbofish) = turbofish {
                if let Some(generics) = generics {
                    interpreter.with_scope(generics, move |interpreter| {
                        let mut turbofish_iter = turbofish.into_iter();

                        for g in generics.params.iter() {
                            match g {
                                syn::GenericParam::Lifetime(_) => {}
                                syn::GenericParam::Type(ty) => {
                                    let new_ty = match turbofish_iter.next() {
                                        None => {
                                            let Some(default_ty) = &ty.default else {
                                                return Err(Error::new_spanned(
                                                    &expr,
                                                    format!("missing default type in {} when called in {}", ty.to_token_stream(), expr.to_token_stream()),
                                                ));
                                            };
                                            interpreter.type_from_syn_type(default_ty, None)?
                                        }
                                        Some(GenericValue::Type(ty)) => ty,
                                        Some(GenericValue::Const(_)) => {
                                            return Err(Error::new_spanned(
                                                expr,
                                                "Received a const when wanting a type.",
                                            ))
                                        }
                                    };
                                    if new_ty == SupportedType::ImplAny {
                                        continue;
                                    }
                                    let Namespace::Type(ty_id) = Namespaces::get_from_current_scope(
                                        interpreter,
                                        &ty.ident,
                                    )?
                                    else {
                                        return Err(Error::new_spanned(
                                            expr,
                                            "generic not a type ???",
                                        ));
                                    };
                                    let (SupportedType::Var(idx), None) =
                                        Typing::get_from_index(interpreter, ty_id, None)?.destructure()
                                    else {
                                        return Err(Error::new_spanned(
                                            expr,
                                            "generic type not a generic type ???",
                                        ));
                                    };
                                    interpreter.generic_context
                                        .set_type_var(idx, new_ty)
                                        .map_err(|x1| Error::new_spanned(ty, x1))?;
                                }
                                syn::GenericParam::Const(c) => {
                                    let new_addr = match turbofish_iter.next() {
                                        None => {
                                            let Some(default_c) = &c.default else {
                                                return Err(Error::new_spanned(
                                                    expr,
                                                    "missing default const",
                                                ));
                                            };
                                            let ptr = interpreter.run_expr(default_c)?;
                                            HandledConst::Computed(interpreter.memory.clone_and_push(ptr)?)
                                        }
                                        Some(GenericValue::Const(addr)) => addr,
                                        Some(GenericValue::Type(_)) => {
                                            return Err(Error::new_spanned(
                                                expr,
                                                "Received a type when wanting a const.",
                                            ))
                                        }
                                    };
                                    let const_idx = Consts::get_from_current_scope(interpreter, &c.ident)?;
                                    let const_val = Consts::get_from_index(interpreter, const_idx, None)?;
                                    let HandledConst::Generic(generic_idx) = const_val else {
                                        return Err(Error::new_spanned(g, "Does not work."))
                                    };
                                    interpreter.generic_context.set_const_var(generic_idx, new_addr).map_err(|_e| Error::new_spanned(
                                        g, "Was already set"
                                    ))?;
                                }
                            }
                        }
                        Ok(())
                    })?
                } else {
                    return Err(Error::new_spanned(
                        &expr,
                        format!(
                            "Turbofish provided, but `{}` cannot have generics.",
                            expr.to_token_stream()
                        ),
                    ));
                }
            }
            f(interpreter)
        })
    }
    pub fn with_generic_context<T>(
        &mut self,
        ctx: &mut GenericContext,
        f: impl FnOnce(&mut Interpreter<'ast>) -> T,
    ) -> T {
        std::mem::swap(&mut self.generic_context, ctx);
        let res = f(self);
        std::mem::swap(&mut self.generic_context, ctx);
        res
    }
}
