use crate::{
    error::Error,
    interpreter::{
        consts::{Consts, HandledConst, UnhandledConst},
        function::{BaseCircuitFunc, Function, HardcodedFunc},
        interpreter_statics::{Functions, Struct, UnhandledStruct},
        library::HardcodedId,
        namespace::{
            Namespace,
            NamespacedKind,
            Namespaces,
            Progress,
            ProgressKind,
            ScopedProgressKind,
            TypeAndGenerics,
            Typing,
            UnHandledType,
        },
        scope::Scope,
        supported_type::SupportedType,
        Interpreter,
    },
    macro_parser::check_macro,
    utils::{get_base_circuit, syn_ty_to_syn_path, Ptr},
};
use arcis_compiler::core::expressions::circuit::BaseCircuitId;
use quote::{format_ident, ToTokens};

impl<'ast> Interpreter<'ast> {
    fn check_generics(&mut self, generics: &'ast syn::Generics, location: &'static str) {
        for param in generics.params.iter() {
            if !matches!(param, syn::GenericParam::Lifetime(_)) {
                let e = Error::new_spanned(
                    param,
                    format!("Lifetimes are the only supported generics on {location}."),
                );
                self.errors.push(e);
            }
        }
    }
}

fn is_use_tree_allowed(t: &syn::UseTree) -> bool {
    match t {
        syn::UseTree::Path(p) => {
            (p.ident == "arcis_imports" || p.ident == "arcis")
                && matches!(*p.tree, syn::UseTree::Glob(_))
        }
        syn::UseTree::Name(_) => false,
        syn::UseTree::Rename(_) => false,
        syn::UseTree::Glob(_) => false,
        syn::UseTree::Group(_) => false,
    }
}

fn get_function<'ast>(
    attrs: &'ast [syn::Attribute],
    signature: &'ast syn::Signature,
    contents: &'ast syn::Block,
    outer_scope: Scope<'ast>,
) -> Result<Function<'ast>, Error> {
    let circ = get_base_circuit(attrs);
    let Some(circ) = circ else {
        return Ok(Function::Ast(signature, contents, outer_scope));
    };
    if !contents.stmts.is_empty() {
        return Err(Error::new_spanned(
            contents,
            "This should be empty in circuit functions.",
        ));
    }
    let syn::Expr::Lit(circ) = circ else {
        return Err(Error::new_spanned(
            circ,
            "Only string literals are supported.",
        ));
    };
    let syn::Lit::Str(circ) = &circ.lit else {
        return Err(Error::new_spanned(circ, "Only strings are supported."));
    };
    let circ_str = circ.value();
    if let Some(circuit_id) = BaseCircuitId::new_from_str(&circ_str) {
        Ok(Function::Circuit(BaseCircuitFunc::new(
            circuit_id,
            signature,
            outer_scope,
        )))
    } else if let Some(hardcoded_id) = HardcodedId::new_from_str(&circ_str) {
        Ok(Function::Hardcoded(HardcodedFunc::new(
            hardcoded_id,
            signature,
            outer_scope,
        )))
    } else {
        Err(Error::new_spanned(circ, "Not a known circuit."))
    }
}

const FORBIDDEN_TRAITS: [&str; 30] = [
    "Drop",
    "Deref",
    "AsRef",
    "AsMut",
    "Into",
    "From",
    "PartialEq",
    "Eq",
    "Default",
    "PartialOrd",
    "Ord",
    "Clone",
    "Fn",
    "FnMut",
    "FnOnce",
    "ToOwned",
    "ToString",
    "Iterator",
    "Extend",
    "IntoIterator",
    "DoubleEndedIterator",
    "ExactSizeIterator",
    "TryFrom",
    "TryInto",
    "FromIterator",
    "Future",
    "IntoFuture",
    "AsyncFn",
    "AsyncFnOnce",
    "AsyncFnMut",
];

fn check_path(intepreter: &mut Interpreter, path: &syn::Path) {
    let first_ident = &path.segments.first().expect("empty path").ident;
    if first_ident == "crate" {
        let e = Error::new_spanned(first_ident, "`crate` is unsupported.");
        intepreter.errors.push(e);
    } else if first_ident == "super" {
        let e = Error::new_spanned(first_ident, "`super` is unsupported.");
        intepreter.errors.push(e);
    }
}

fn scope_generics<'ast>(
    interpreter: &mut Interpreter<'ast>,
    generics: &'ast syn::Generics,
) -> Scope<'ast> {
    let outer_scope = generics.into();
    interpreter.scope_info.push_static(outer_scope);
    for g in generics.params.iter() {
        match g {
            syn::GenericParam::Lifetime(_) => {}
            syn::GenericParam::Type(t) => {
                if let Some(ty) = &t.default {
                    interpreter.errors.push(Error::new_spanned(
                        ty,
                        "Default types are not allowed in generic types.",
                    ));
                }
                let n = interpreter.generic_context.new_type_var();
                let ty_id = Typing::insert_inner(
                    interpreter,
                    Progress::Output(TypeAndGenerics::new(SupportedType::Var(n), None)),
                );
                Namespaces::insert_with_namespace(
                    interpreter,
                    t.ident.clone(),
                    Namespace::Scope(outer_scope),
                    Namespace::Type(ty_id),
                )
            }
            syn::GenericParam::Const(c) => {
                let n = interpreter.generic_context.new_const_var();
                Consts::insert_handled(interpreter, c.ident.clone(), HandledConst::Generic(n));
            }
        }
    }
    outer_scope
}

impl<'ast> syn::visit::Visit<'ast> for Interpreter<'ast> {
    fn visit_attribute(&mut self, _: &'ast syn::Attribute) {
        // We do nothing. We do not visit sub attributes.
    }
    fn visit_file(&mut self, file: &'ast syn::File) {
        let scope = file.into();
        self.scope_info.push_static(scope);
        syn::visit::visit_file(self, file);
        self.scope_info.pop(scope)
    }
    fn visit_block(&mut self, block: &'ast syn::Block) {
        let scope = block.into();
        self.scope_info.push_static(scope);
        syn::visit::visit_block(self, block);
        self.scope_info.pop(scope);
    }
    fn visit_item(&mut self, i: &'ast syn::Item) {
        match i {
            syn::Item::Const(_)
            | syn::Item::Fn(_)
            | syn::Item::Struct(_)
            | syn::Item::Impl(_)
            | syn::Item::Mod(_)
            | syn::Item::Trait(_)
            | syn::Item::Type(_)
            | syn::Item::Use(_) => {
                syn::visit::visit_item(self, i);
            }
            syn::Item::Verbatim(item) => {
                let e =
                    Error::new_spanned(item, format!("Unknown item `{}` is unsupported.", item));
                self.errors.push(e);
            }
            _ => {
                syn::visit::visit_item(self, i);
                // Sanity check that we did not forget an item to have an error message.
                if self.errors.is_empty() {
                    let e = Error::new_spanned(i, "Unsupported item.");
                    self.errors.push(e);
                }
            }
        }
    }
    fn visit_item_const(&mut self, i: &'ast syn::ItemConst) {
        Consts::insert_unhandled(
            self,
            i.ident.clone(),
            UnhandledConst::Normal(&i.expr, self.scope_info.current_scope()),
        );
        syn::visit::visit_item_const(self, i);
    }
    fn visit_item_fn(&mut self, i: &'ast syn::ItemFn) {
        let generics = &i.sig.generics;
        let outer_scope = scope_generics(self, generics);
        syn::visit::visit_item_fn(self, i);
        self.scope_info.pop(outer_scope);
        let function = get_function(&i.attrs, &i.sig, &i.block, outer_scope);
        match function {
            Ok(function) => self.insert_function(i.sig.ident.clone(), function),
            Err(e) => {
                self.errors.push(e);
            }
        }
    }
    fn visit_item_mod(&mut self, i: &'ast syn::ItemMod) {
        if i.attrs.iter().any(|x| {
            if let syn::Meta::Path(path) = &x.meta {
                if let Some(ident) = path.get_ident() {
                    if ident == "encrypted_library" {
                        return true;
                    }
                }
            }
            false
        }) {
            syn::visit::visit_item_mod(self, i);
        } else {
            let scope = i.into();
            self.namespaces.insert_with_scope(
                i.ident.clone(),
                self.scope_info.current_scope(),
                Namespace::Scope(scope),
            );
            self.scope_info.push_static(scope);
            syn::visit::visit_item_mod(self, i);
            self.scope_info.pop(scope);
        }
    }
    fn visit_item_struct(&mut self, i: &'ast syn::ItemStruct) {
        let ident = &i.ident;
        let scope = self.scope_info.current_scope();
        let generic_scope = scope_generics(self, &i.generics);
        syn::visit::visit_item_struct(self, i);
        self.scope_info.pop(generic_scope);

        let id = Struct::insert_inner(
            self,
            Progress::Input(UnhandledStruct::new(i, generic_scope)),
        );
        let ty_id = Typing::insert_inner(self, Progress::Input(UnHandledType::new_struct(id)));
        self.namespaces
            .insert_with_scope(ident.clone(), scope, Namespace::Type(ty_id));
        match i.fields {
            syn::Fields::Named(_) => {
                // nothing to do
            }
            syn::Fields::Unnamed(_) => {
                self.insert_function(ident.clone(), Function::StructRef(ty_id, Some(&i.generics)));
            }
            syn::Fields::Unit => {
                Consts::insert_unhandled(
                    self,
                    ident.clone(),
                    UnhandledConst::UnitStruct(ty_id, Ptr(i)),
                );
            }
        }
    }
    fn visit_item_type(&mut self, i: &'ast syn::ItemType) {
        let outer_scope = self.scope_info.current_scope();
        let inner_scope = scope_generics(self, &i.generics);
        let ty_id = Typing::insert_inner(
            self,
            Progress::Input(UnHandledType::Impl {
                scope: inner_scope,
                ty: i.ty.as_ref(),
                generics: Some(&i.generics),
            }),
        );
        Namespaces::insert_with_namespace(
            self,
            i.ident.clone(),
            Namespace::Scope(outer_scope),
            Namespace::Type(ty_id),
        );
        syn::visit::visit_item_type(self, i);
        self.scope_info.pop(inner_scope);
    }
    fn visit_item_impl(&mut self, i: &'ast syn::ItemImpl) {
        if let Some((_, t, _)) = &i.trait_ {
            let last_ident = &t.segments.last().expect("empty path").ident;
            if FORBIDDEN_TRAITS.iter().any(|x| last_ident == *x) {
                self.errors.push(Error::new_spanned(
                    t,
                    format!("Trait {} cannot be implemented.", last_ident),
                ));
            }
            check_path(self, t);
        }
        if let syn::Type::Reference(ty) = i.self_ty.as_ref() {
            self.errors.push(Error::new_spanned(
                ty.and_token,
                "References are unsupported in impls.",
            ));
        }
        let scope = scope_generics(self, &i.generics);
        let syn_self_ty = &i.self_ty;
        let self_ty_id = Typing::insert_inner(
            self,
            Progress::Input(UnHandledType::new_impl(syn_self_ty, scope, None)),
        );
        let self_ident = format_ident!("Self");
        Namespaces::insert_with_namespace(
            self,
            self_ident.clone(),
            Namespace::Scope(scope),
            Namespace::Type(self_ty_id),
        );
        if let Some(path) = syn_ty_to_syn_path(&i.self_ty) {
            Consts::insert_unhandled(
                self,
                self_ident.clone(),
                UnhandledConst::SelfIdent(path, scope),
            );
        }
        for item in &i.items {
            match item {
                syn::ImplItem::Const(c) => {
                    let c_name = &c.ident;
                    Consts::insert_unhandled_in_type(
                        self,
                        c_name.clone(),
                        UnhandledConst::Normal(&c.expr, scope),
                        self_ty_id,
                    );
                }
                syn::ImplItem::Fn(f) => {
                    let generics = &f.sig.generics;
                    let outer_scope = scope_generics(self, generics);

                    let function = get_function(&f.attrs, &f.sig, &f.block, outer_scope);
                    match function {
                        Ok(function) => {
                            let f_name = &f.sig.ident;
                            self.insert_method(f_name.clone(), self_ty_id, function);
                        }
                        Err(e) => {
                            self.errors.push(e);
                        }
                    }
                    self.scope_info.pop(outer_scope);
                }
                syn::ImplItem::Type(t) => {
                    let ty_id = Typing::insert_inner(
                        self,
                        Progress::Input(UnHandledType::new_impl(&t.ty, scope, Some(&t.generics))),
                    );
                    Namespaces::insert_with_namespace(
                        self,
                        t.ident.clone(),
                        Namespace::Type(self_ty_id),
                        Namespace::Type(ty_id),
                    );
                }
                _ => {
                    let e = Error::new_spanned(item, "Unsupported item in impl.");
                    self.errors.push(e);
                }
            }
        }
        syn::visit::visit_item_impl(self, i);
        self.scope_info.pop(scope);
    }
    fn visit_impl_item_fn(&mut self, i: &'ast syn::ImplItemFn) {
        self.with_scope(&i.sig.generics, |interpreter| {
            syn::visit::visit_impl_item_fn(interpreter, i);
        })
    }
    fn visit_expr(&mut self, i: &'ast syn::Expr) {
        if let syn::Expr::Verbatim(item) = i {
            let e = Error::new_spanned(
                item,
                format!("Unknown expression `{}` is unsupported.", item),
            );
            self.errors.push(e);
        }
        syn::visit::visit_expr(self, i);
    }
    fn visit_expr_async(&mut self, i: &'ast syn::ExprAsync) {
        let e = Error::new_spanned(i.async_token, "`async` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_async(self, i);
    }
    fn visit_expr_await(&mut self, i: &'ast syn::ExprAwait) {
        let e = Error::new_spanned(i.await_token, "`await` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_await(self, i);
    }
    fn visit_expr_break(&mut self, i: &'ast syn::ExprBreak) {
        let e = Error::new_spanned(i.break_token, "`break` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_break(self, i);
    }
    fn visit_expr_continue(&mut self, i: &'ast syn::ExprContinue) {
        let e = Error::new_spanned(i.continue_token, "`continue` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_continue(self, i);
    }
    fn visit_expr_let(&mut self, i: &'ast syn::ExprLet) {
        let e = Error::new_spanned(i.let_token, "`if let` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_let(self, i);
    }
    fn visit_expr_loop(&mut self, i: &'ast syn::ExprLoop) {
        let e = Error::new_spanned(i.loop_token, "`loop` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_loop(self, i);
    }
    fn visit_macro(&mut self, i: &'ast syn::Macro) {
        if let Err(e) = check_macro(i) {
            self.errors.push(e);
        }
        syn::visit::visit_macro(self, i);
    }
    fn visit_expr_match(&mut self, i: &'ast syn::ExprMatch) {
        let e = Error::new_spanned(i.match_token, "`match` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_match(self, i);
    }
    fn visit_expr_raw_addr(&mut self, i: &'ast syn::ExprRawAddr) {
        let e = Error::new_spanned(i.raw, "`raw` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_raw_addr(self, i);
    }
    fn visit_expr_return(&mut self, i: &'ast syn::ExprReturn) {
        let e = Error::new_spanned(i.return_token, "`return` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_return(self, i);
    }
    fn visit_expr_try(&mut self, i: &'ast syn::ExprTry) {
        let e = Error::new_spanned(i.question_token, "`?` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_try(self, i);
    }
    fn visit_expr_try_block(&mut self, i: &'ast syn::ExprTryBlock) {
        let e = Error::new_spanned(i.try_token, "`try` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_try_block(self, i);
    }
    fn visit_expr_unsafe(&mut self, i: &'ast syn::ExprUnsafe) {
        let e = Error::new_spanned(i.unsafe_token, "`unsafe` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_unsafe(self, i);
    }
    fn visit_expr_while(&mut self, i: &'ast syn::ExprWhile) {
        let e = Error::new_spanned(i.while_token, "`while` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_while(self, i);
    }
    fn visit_expr_yield(&mut self, i: &'ast syn::ExprYield) {
        let e = Error::new_spanned(i.yield_token, "`yield` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_expr_yield(self, i);
    }
    fn visit_bin_op(&mut self, i: &'ast syn::BinOp) {
        if matches!(i, syn::BinOp::Shl(_) | syn::BinOp::ShlAssign(_)) {
            let e = Error::new_spanned(i, "`<<` is unsupported.");
            self.errors.push(e);
        }
    }
    fn visit_lit_str(&mut self, i: &'ast syn::LitStr) {
        let val = i.value();
        let e = if val.is_ascii() {
            Error::new_spanned(
                i,
                format!(
                    "`\"...\"` is unsupported. You can write your string as b\"{}\" though.",
                    val
                ),
            )
        } else {
            Error::new_spanned(i, "`\"...\"` is unsupported.")
        };
        self.errors.push(e);
    }
    fn visit_lit_cstr(&mut self, i: &'ast syn::LitCStr) {
        let e = Error::new_spanned(i, "`c\"...\"` is unsupported.");
        self.errors.push(e);
    }
    fn visit_lit_char(&mut self, i: &'ast syn::LitChar) {
        let e = Error::new_spanned(i, "'...' is unsupported.");
        self.errors.push(e);
    }
    fn visit_item_enum(&mut self, i: &'ast syn::ItemEnum) {
        let e = Error::new_spanned(i.enum_token, "`enum` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_enum(self, i);
    }
    fn visit_item_extern_crate(&mut self, i: &'ast syn::ItemExternCrate) {
        let e = Error::new_spanned(i.extern_token, "`extern` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_extern_crate(self, i);
    }
    fn visit_item_foreign_mod(&mut self, i: &'ast syn::ItemForeignMod) {
        let e = Error::new_spanned(&i.abi, "Foreign mods are unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_foreign_mod(self, i);
    }
    fn visit_item_static(&mut self, i: &'ast syn::ItemStatic) {
        let e = Error::new_spanned(i.static_token, "`static` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_static(self, i);
    }
    fn visit_item_trait(&mut self, i: &'ast syn::ItemTrait) {
        self.check_generics(&i.generics, "user-defined trait");
        let scope = self.scope_info.current_scope();
        Namespaces::insert_with_namespace(
            self,
            i.ident.clone(),
            Namespace::Scope(scope),
            Namespace::Forbidden("Traits cannot be accessed directly yet."),
        );
        let all = Typing::insert_inner(
            self,
            Progress::Output(TypeAndGenerics::new(SupportedType::ImplAny, None)),
        );
        let namespace = Namespace::Type(all);
        for item in &i.items {
            match item {
                syn::TraitItem::Const(c) => {
                    if let Some((_, expr)) = &c.default {
                        Consts::insert_unhandled_in_type(
                            self,
                            c.ident.clone(),
                            UnhandledConst::Normal(expr, scope),
                            all,
                        );
                    }
                }
                syn::TraitItem::Fn(c) => {
                    if let Some(block) = &c.default {
                        Functions::insert_handled_in_type(
                            self,
                            c.sig.ident.clone(),
                            Function::Ast(&c.sig, block, scope),
                            all,
                        );
                    }
                }
                syn::TraitItem::Type(c) => {
                    if let Some((_, ty)) = &c.default {
                        let ty_id = Typing::insert_inner(
                            self,
                            Progress::Input(UnHandledType::new_impl(ty, scope, Some(&c.generics))),
                        );
                        Namespaces::insert_with_namespace(
                            self,
                            c.ident.clone(),
                            namespace,
                            Namespace::Type(ty_id),
                        );
                    }
                }
                syn::TraitItem::Verbatim(t) => self
                    .errors
                    .push(Error::new_spanned(t, format!("`{}` is unsupported.", t))),
                _ => {
                    self.errors.push(Error::new_spanned(
                        item,
                        format!("`{}` is unsupported.", item.to_token_stream()),
                    ));
                }
            }
            syn::visit::visit_trait_item(self, item);
        }
        syn::visit::visit_item_trait(self, i);
    }
    fn visit_item_trait_alias(&mut self, i: &'ast syn::ItemTraitAlias) {
        let e = Error::new_spanned(i.trait_token, "`trait` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_trait_alias(self, i);
    }
    fn visit_item_union(&mut self, i: &'ast syn::ItemUnion) {
        let e = Error::new_spanned(i.union_token, "`union` is unsupported.");
        self.errors.push(e);
        syn::visit::visit_item_union(self, i);
    }
    fn visit_item_use(&mut self, i: &'ast syn::ItemUse) {
        if !is_use_tree_allowed(&i.tree) {
            let e = Error::new_spanned(
                i,
                format!(
                    "Unsupported `use`: `{}`. Only `use arcis::*` is supported.",
                    i.to_token_stream()
                ),
            );
            self.errors.push(e);
        }
    }
    fn visit_local_init(&mut self, i: &'ast syn::LocalInit) {
        if let Some((token, _)) = &i.diverge {
            let e =
                Error::new_spanned(token, "`else` in `let a = b else { ... };` is unsupported.");
            self.errors.push(e);
        }
        syn::visit::visit_local_init(self, i);
    }
    fn visit_type_path(&mut self, i: &'ast syn::TypePath) {
        check_path(self, &i.path);
        syn::visit::visit_type_path(self, i);
    }
}
