use std::iter;
use serde::Deserialize;
use swc_common::{comments::Comments, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, helper_expr, perf::Check};
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::{
    contains_this_expr, find_pat_ids,
    function::{FnEnvHoister, FnWrapperResult, FunctionWrapper},
    private_ident, quote_ident, ExprFactory, Remapper, StmtLike,
};
use swc_ecma_visit::{
    as_folder, noop_visit_mut_type, noop_visit_type, Fold, Visit, VisitMut, VisitMutWith, VisitWith,
};
use swc_trace_macro::swc_trace;
#[tracing::instrument(level = "info", skip_all)]
pub fn async_to_generator<C: Comments + Clone>(
    c: Config,
    comments: Option<C>,
    unresolved_mark: Mark,
) -> impl Fold + VisitMut {
    as_folder(AsyncToGenerator {
        c,
        comments,
        unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
    })
}
#[derive(Debug, Clone, Copy, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
    #[serde(default)]
    pub ignore_function_name: bool,
    #[serde(default)]
    pub ignore_function_length: bool,
}
#[derive(Default, Clone)]
struct AsyncToGenerator<C: Comments + Clone> {
    c: Config,
    comments: Option<C>,
    unresolved_ctxt: SyntaxContext,
}
struct Actual<C: Comments> {
    c: Config,
    comments: Option<C>,
    unresolved_ctxt: SyntaxContext,
    extra_stmts: Vec<Stmt>,
    hoist_stmts: Vec<Stmt>,
}
#[swc_trace]
#[fast_path(ShouldWork)]
impl<C: Comments + Clone> VisitMut for AsyncToGenerator<C> {
    noop_visit_mut_type!();
    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
        self.visit_mut_stmt_like(n);
    }
    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
        self.visit_mut_stmt_like(n);
    }
}
#[swc_trace]
impl<C: Comments + Clone> AsyncToGenerator<C> {
    fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
    where
        T: StmtLike + VisitMutWith<Actual<C>>,
        Vec<T>: VisitMutWith<Self>,
    {
        let mut stmts_updated = Vec::with_capacity(stmts.len());
        for mut stmt in stmts.drain(..) {
            let mut actual = Actual {
                c: self.c,
                comments: self.comments.clone(),
                unresolved_ctxt: self.unresolved_ctxt,
                extra_stmts: vec![],
                hoist_stmts: vec![],
            };
            stmt.visit_mut_with(&mut actual);
            stmts_updated.extend(actual.hoist_stmts.into_iter().map(T::from_stmt));
            stmts_updated.push(stmt);
            stmts_updated.extend(actual.extra_stmts.into_iter().map(T::from_stmt));
        }
        *stmts = stmts_updated;
        stmts.visit_mut_children_with(self);
    }
}
#[swc_trace]
#[fast_path(ShouldWork)]
impl<C: Comments> VisitMut for Actual<C> {
    noop_visit_mut_type!();
    fn visit_mut_class_method(&mut self, m: &mut ClassMethod) {
        if m.function.body.is_none() {
            return;
        }
        m.visit_mut_children_with(self);
        if m.kind != MethodKind::Method || !m.function.is_async {
            return;
        }
        let params = m.function.params.clone();
        let mut visitor = FnEnvHoister::new(self.unresolved_ctxt);
        m.function.params.clear();
        m.function.body.visit_mut_with(&mut visitor);
        let expr = make_fn_ref(FnExpr {
            ident: None,
            function: m.function.take(),
        });
        m.function = Function {
            span: m.span,
            is_async: false,
            is_generator: false,
            params,
            body: Some(BlockStmt {
                span: DUMMY_SP,
                stmts: visitor
                    .to_stmt()
                    .into_iter()
                    .chain(iter::once(Stmt::Return(ReturnStmt {
                        span: DUMMY_SP,
                        arg: Some(Box::new(Expr::Call(CallExpr {
                            span: DUMMY_SP,
                            callee: expr.as_callee(),
                            args: vec![],
                            type_args: Default::default(),
                        }))),
                    })))
                    .collect(),
            }),
            decorators: Default::default(),
            type_params: Default::default(),
            return_type: Default::default(),
        }
        .into();
    }
    fn visit_mut_call_expr(&mut self, expr: &mut CallExpr) {
        if let Callee::Expr(e) = &mut expr.callee {
            let mut e = &mut **e;
            while let Expr::Paren(ParenExpr { expr, .. }) = e {
                e = &mut **expr;
            }
            self.visit_mut_expr_with_binding(e, None, true);
        }
        expr.args.visit_mut_with(self)
    }
    fn visit_mut_expr(&mut self, expr: &mut Expr) {
        self.visit_mut_expr_with_binding(expr, None, false);
    }
    fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
        f.visit_mut_children_with(self);
        if !f.function.is_async {
            return;
        }
        let mut wrapper = FunctionWrapper::from(f.take());
        wrapper.ignore_function_name = self.c.ignore_function_name;
        wrapper.ignore_function_length = self.c.ignore_function_length;
        let fn_expr = wrapper.function.fn_expr().unwrap();
        wrapper.function = make_fn_ref(fn_expr);
        let FnWrapperResult { name_fn, ref_fn } = wrapper.into();
        *f = name_fn;
        self.extra_stmts.push(Stmt::Decl(ref_fn.into()));
    }
    fn visit_mut_module_item(&mut self, item: &mut ModuleItem) {
        match item {
            ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default)) => {
                if let DefaultDecl::Fn(expr) = &mut export_default.decl {
                    if expr.function.is_async {
                        let mut wrapper = FunctionWrapper::from(expr.take());
                        wrapper.ignore_function_name = self.c.ignore_function_name;
                        wrapper.ignore_function_length = self.c.ignore_function_length;
                        let fn_expr = wrapper.function.fn_expr().unwrap();
                        wrapper.function = make_fn_ref(fn_expr);
                        let FnWrapperResult { name_fn, ref_fn } = wrapper.into();
                        *item = ModuleItem::ModuleDecl(
                            ExportDefaultDecl {
                                span: export_default.span,
                                decl: name_fn.into(),
                            }
                            .into(),
                        );
                        self.extra_stmts.push(Stmt::Decl(ref_fn.into()));
                    };
                } else {
                    export_default.visit_mut_children_with(self);
                }
            }
            _ => item.visit_mut_children_with(self),
        };
    }
    fn visit_mut_method_prop(&mut self, prop: &mut MethodProp) {
        prop.visit_mut_children_with(self);
        if !prop.function.is_async {
            return;
        }
        let is_this_used = contains_this_expr(&prop.function.body);
        let original_fn_params = prop.function.params.take();
        let prop_method_span = prop.function.span;
        let prop_method_body_span = if let Some(body) = &prop.function.body {
            body.span
        } else {
            DUMMY_SP
        };
        prop.function.span = prop_method_body_span;
        let fn_ref = make_fn_ref(
            Function {
                params: vec![],
                ..*prop.function.take()
            }
            .into(),
        );
        let fn_ref = if is_this_used {
            fn_ref.apply(
                DUMMY_SP,
                Box::new(Expr::This(ThisExpr { span: DUMMY_SP })),
                vec![],
            )
        } else {
            Expr::Call(CallExpr {
                span: DUMMY_SP,
                callee: fn_ref.as_callee(),
                args: vec![],
                type_args: Default::default(),
            })
        };
        prop.function = Function {
            params: original_fn_params,
            span: prop_method_span,
            is_async: false,
            is_generator: false,
            body: Some(BlockStmt {
                span: DUMMY_SP,
                stmts: vec![Stmt::Return(ReturnStmt {
                    span: DUMMY_SP,
                    arg: Some(Box::new(fn_ref)),
                })],
            }),
            decorators: Default::default(),
            return_type: Default::default(),
            type_params: Default::default(),
        }
        .into()
    }
    fn visit_mut_stmts(&mut self, _n: &mut Vec<Stmt>) {}
    fn visit_mut_var_declarator(&mut self, var: &mut VarDeclarator) {
        if let VarDeclarator {
            name: Pat::Ident(BindingIdent { id, .. }),
            init: Some(init),
            ..
        } = var
        {
            match init.as_ref() {
                Expr::Fn(FnExpr {
                    ident: None,
                    ref function,
                }) if function.is_async || function.is_generator => {
                    self.visit_mut_expr_with_binding(init, Some(id.clone()), false);
                    return;
                }
                Expr::Arrow(arrow_expr) if arrow_expr.is_async || arrow_expr.is_generator => {
                    self.visit_mut_expr_with_binding(init, Some(id.clone()), false);
                    return;
                }
                _ => {}
            }
        }
        var.visit_mut_children_with(self);
    }
}
#[swc_trace]
impl<C: Comments> Actual<C> {
    fn visit_mut_expr_with_binding(
        &mut self,
        expr: &mut Expr,
        binding_ident: Option<Ident>,
        in_iife: bool,
    ) {
        expr.visit_mut_children_with(self);
        match expr {
            Expr::Arrow(arrow_expr @ ArrowExpr { is_async: true, .. }) => {
                let mut state = FnEnvHoister::new(self.unresolved_ctxt);
                arrow_expr.visit_mut_with(&mut state);
                self.hoist_stmts.extend(state.to_stmt());
                let mut wrapper = FunctionWrapper::from(arrow_expr.take());
                wrapper.ignore_function_name = self.c.ignore_function_name;
                wrapper.ignore_function_length = self.c.ignore_function_length;
                wrapper.binding_ident = binding_ident;
                let fn_expr = wrapper.function.expect_fn_expr();
                wrapper.function = make_fn_ref(fn_expr);
                *expr = wrapper.into();
                if !in_iife {
                    if let Some(c) = &mut self.comments {
                        c.add_pure_comment(expr.span().lo)
                    }
                }
            }
            Expr::Fn(fn_expr) if fn_expr.function.is_async => {
                let mut wrapper = FunctionWrapper::from(fn_expr.take());
                wrapper.ignore_function_name = self.c.ignore_function_name;
                wrapper.ignore_function_length = self.c.ignore_function_length;
                wrapper.binding_ident = binding_ident;
                let fn_expr = wrapper.function.expect_fn_expr();
                wrapper.function = make_fn_ref(fn_expr);
                *expr = wrapper.into();
                if !in_iife {
                    if let Some(c) = &mut self.comments {
                        c.add_pure_comment(expr.span().lo)
                    }
                }
            }
            _ => {}
        }
    }
}
#[tracing::instrument(level = "info", skip_all)]
fn make_fn_ref(mut expr: FnExpr) -> Expr {
    {
        let param_ids: Vec<Id> = find_pat_ids(&expr.function.params);
        let mapping = param_ids
            .into_iter()
            .map(|id| (id, SyntaxContext::empty().apply_mark(Mark::new())))
            .collect();
        expr.function.visit_mut_with(&mut Remapper::new(&mapping));
    }
    expr.function.body.visit_mut_with(&mut AsyncFnBodyHandler {
        is_async_generator: expr.function.is_generator,
    });
    assert!(expr.function.is_async);
    expr.function.is_async = false;
    let helper = if expr.function.is_generator {
        helper!(wrap_async_generator, "wrapAsyncGenerator")
    } else {
        helper!(async_to_generator, "asyncToGenerator")
    };
    expr.function.is_generator = true;
    let span = expr.span();
    let expr = Expr::Fn(expr);
    Expr::Call(CallExpr {
        span,
        callee: helper,
        args: vec![expr.as_arg()],
        type_args: Default::default(),
    })
}
struct AsyncFnBodyHandler {
    is_async_generator: bool,
}
macro_rules! noop {
    ($name:ident, $T:path) => {
        fn $name(&mut self, _f: &mut $T) {}
    };
}
#[swc_trace]
impl VisitMut for AsyncFnBodyHandler {
    noop_visit_mut_type!();
    noop!(visit_mut_fn_expr, FnExpr);
    noop!(visit_mut_constructor, Constructor);
    noop!(visit_mut_arrow_expr, ArrowExpr);
    noop!(visit_mut_fn_decl, FnDecl);
    fn visit_mut_expr(&mut self, expr: &mut Expr) {
        expr.visit_mut_children_with(self);
        match expr {
            Expr::Yield(YieldExpr {
                span,
                arg: Some(arg),
                delegate: true,
            }) => {
                let callee = helper!(async_generator_delegate, "asyncGeneratorDelegate");
                let arg = Box::new(Expr::Call(CallExpr {
                    span: *span,
                    callee,
                    args: vec![
                        CallExpr {
                            span: DUMMY_SP,
                            callee: helper!(async_iterator, "asyncIterator"),
                            args: vec![arg.take().as_arg()],
                            type_args: Default::default(),
                        }
                        .as_arg(),
                        helper_expr!(await_async_generator, "awaitAsyncGenerator").as_arg(),
                    ],
                    type_args: Default::default(),
                }));
                *expr = Expr::Yield(YieldExpr {
                    span: *span,
                    delegate: true,
                    arg: Some(arg),
                })
            }
            Expr::Await(AwaitExpr { span, arg }) => {
                if self.is_async_generator {
                    let callee = helper!(await_async_generator, "awaitAsyncGenerator");
                    let arg = Box::new(Expr::Call(CallExpr {
                        span: *span,
                        callee,
                        args: vec![arg.take().as_arg()],
                        type_args: Default::default(),
                    }));
                    *expr = Expr::Yield(YieldExpr {
                        span: *span,
                        delegate: false,
                        arg: Some(arg),
                    })
                } else {
                    *expr = Expr::Yield(YieldExpr {
                        span: *span,
                        delegate: false,
                        arg: Some(arg.take()),
                    })
                }
            }
            _ => {}
        }
    }
    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
        s.visit_mut_children_with(self);
        handle_await_for(s, self.is_async_generator);
    }
}
#[derive(Default)]
struct ShouldWork {
    found: bool,
}
#[swc_trace]
impl Visit for ShouldWork {
    noop_visit_type!();
    fn visit_function(&mut self, f: &Function) {
        if f.is_async {
            self.found = true;
            return;
        }
        f.visit_children_with(self);
    }
    fn visit_arrow_expr(&mut self, f: &ArrowExpr) {
        if f.is_async {
            self.found = true;
            return;
        }
        f.visit_children_with(self);
    }
}
impl Check for ShouldWork {
    fn should_handle(&self) -> bool {
        self.found
    }
}
#[tracing::instrument(level = "info", skip_all)]
fn handle_await_for(stmt: &mut Stmt, is_async_generator: bool) {
    let s = match stmt {
        Stmt::ForOf(
            s @ ForOfStmt {
                await_token: Some(..),
                ..
            },
        ) => s.take(),
        _ => return,
    };
    let value = private_ident!("_value");
    let iterator = private_ident!("_iterator");
    let iterator_error = private_ident!("_iteratorError");
    let step = private_ident!("_step");
    let did_iteration_error = private_ident!("_didIteratorError");
    let iterator_abrupt_completion = private_ident!("_iteratorAbruptCompletion");
    let err_param = private_ident!("err");
    let try_body = {
        let body_span = s.body.span();
        let orig_body = match *s.body {
            Stmt::Block(s) => s.stmts,
            _ => vec![*s.body],
        };
        let mut for_loop_body = vec![];
        {
            let value_var = VarDeclarator {
                span: DUMMY_SP,
                name: value.clone().into(),
                init: Some(Box::new(step.clone().make_member(quote_ident!("value")))),
                definite: false,
            };
            for_loop_body.push(
                VarDecl {
                    span: DUMMY_SP,
                    kind: VarDeclKind::Let,
                    declare: false,
                    decls: vec![value_var],
                }
                .into(),
            );
        }
        match s.left {
            VarDeclOrPat::VarDecl(v) => {
                let var = v.decls.into_iter().next().unwrap();
                let var_decl = VarDeclarator {
                    span: DUMMY_SP,
                    name: var.name,
                    init: Some(Box::new(Expr::Ident(value))),
                    definite: false,
                };
                for_loop_body.push(
                    VarDecl {
                        span: DUMMY_SP,
                        kind: VarDeclKind::Const,
                        declare: false,
                        decls: vec![var_decl],
                    }
                    .into(),
                );
            }
            VarDeclOrPat::Pat(p) => {
                for_loop_body.push(Stmt::Expr(ExprStmt {
                    span: DUMMY_SP,
                    expr: Box::new(Expr::Assign(AssignExpr {
                        span: DUMMY_SP,
                        op: op!("="),
                        left: PatOrExpr::Pat(p),
                        right: Box::new(Expr::Ident(value)),
                    })),
                }));
            }
        }
        for_loop_body.extend(orig_body);
        let for_loop_body = BlockStmt {
            span: body_span,
            stmts: for_loop_body,
        };
        let mut init_var_decls = vec![];
        init_var_decls.push(VarDeclarator {
            span: DUMMY_SP,
            name: iterator.clone().into(),
            init: {
                let callee = helper!(async_iterator, "asyncIterator");
                Some(Box::new(Expr::Call(CallExpr {
                    span: DUMMY_SP,
                    callee,
                    args: vec![s.right.as_arg()],
                    type_args: Default::default(),
                })))
            },
            definite: false,
        });
        init_var_decls.push(VarDeclarator {
            span: DUMMY_SP,
            name: step.clone().into(),
            init: None,
            definite: false,
        });
        let for_stmt = Stmt::For(ForStmt {
            span: s.span,
            init: Some(
                VarDecl {
                    span: DUMMY_SP,
                    kind: VarDeclKind::Var,
                    declare: false,
                    decls: init_var_decls,
                }
                .into(),
            ),
            test: {
                let iter_next = iterator.clone().make_member(quote_ident!("next"));
                let iter_next = CallExpr {
                    span: DUMMY_SP,
                    callee: iter_next.as_callee(),
                    args: Default::default(),
                    type_args: Default::default(),
                };
                let yield_arg = if is_async_generator {
                    Box::new(Expr::Call(CallExpr {
                        span: DUMMY_SP,
                        callee: helper!(await_async_generator, "awaitAsyncGenerator"),
                        args: vec![iter_next.as_arg()],
                        type_args: Default::default(),
                    }))
                } else {
                    Box::new(Expr::Call(iter_next))
                };
                let assign_to_step = Expr::Assign(AssignExpr {
                    span: DUMMY_SP,
                    op: op!("="),
                    left: PatOrExpr::Pat(step.into()),
                    right: Box::new(Expr::Yield(YieldExpr {
                        span: DUMMY_SP,
                        arg: Some(yield_arg),
                        delegate: false,
                    })),
                });
                let right = Box::new(Expr::Unary(UnaryExpr {
                    span: DUMMY_SP,
                    op: op!("!"),
                    arg: Box::new(assign_to_step.make_member(quote_ident!("done"))),
                }));
                let left = PatOrExpr::Pat(iterator_abrupt_completion.clone().into());
                Some(Box::new(Expr::Assign(AssignExpr {
                    span: DUMMY_SP,
                    op: op!("="),
                    left,
                    right,
                })))
            },
            update: Some(Box::new(Expr::Assign(AssignExpr {
                span: DUMMY_SP,
                op: op!("="),
                left: PatOrExpr::Pat(iterator_abrupt_completion.clone().into()),
                right: false.into(),
            }))),
            body: Box::new(Stmt::Block(for_loop_body)),
        });
        BlockStmt {
            span: body_span,
            stmts: vec![for_stmt],
        }
    };
    let catch_clause = {
        let mark_as_errorred = Stmt::Expr(ExprStmt {
            span: DUMMY_SP,
            expr: Box::new(Expr::Assign(AssignExpr {
                span: DUMMY_SP,
                op: op!("="),
                left: PatOrExpr::Pat(did_iteration_error.clone().into()),
                right: true.into(),
            })),
        });
        let store_error = Stmt::Expr(ExprStmt {
            span: DUMMY_SP,
            expr: Box::new(Expr::Assign(AssignExpr {
                span: DUMMY_SP,
                op: op!("="),
                left: PatOrExpr::Pat(iterator_error.clone().into()),
                right: Box::new(Expr::Ident(err_param.clone())),
            })),
        });
        CatchClause {
            span: DUMMY_SP,
            param: Some(err_param.into()),
            body: BlockStmt {
                span: DUMMY_SP,
                stmts: vec![mark_as_errorred, store_error],
            },
        }
    };
    let finally_block = {
        let throw_iterator_error = Stmt::Throw(ThrowStmt {
            span: DUMMY_SP,
            arg: Box::new(Expr::Ident(iterator_error.clone())),
        });
        let throw_iterator_error = Stmt::If(IfStmt {
            span: DUMMY_SP,
            test: Box::new(Expr::Ident(did_iteration_error.clone())),
            cons: Box::new(Stmt::Block(BlockStmt {
                span: DUMMY_SP,
                stmts: vec![throw_iterator_error],
            })),
            alt: None,
        });
        let yield_stmt = Stmt::Expr(ExprStmt {
            span: DUMMY_SP,
            expr: Box::new(Expr::Yield(YieldExpr {
                span: DUMMY_SP,
                delegate: false,
                arg: Some(Box::new(Expr::Call(CallExpr {
                    span: DUMMY_SP,
                    callee: iterator
                        .clone()
                        .make_member(quote_ident!("return"))
                        .as_callee(),
                    args: Default::default(),
                    type_args: Default::default(),
                }))),
            })),
        });
        let conditional_yield = Stmt::If(IfStmt {
            span: DUMMY_SP,
            test: Box::new(Expr::Bin(BinExpr {
                span: DUMMY_SP,
                op: op!("&&"),
                left: Box::new(Expr::Ident(iterator_abrupt_completion.clone())),
                right: Box::new(Expr::Bin(BinExpr {
                    span: DUMMY_SP,
                    op: op!("!="),
                    left: Box::new(iterator.make_member(quote_ident!("return"))),
                    right: Null { span: DUMMY_SP }.into(),
                })),
            })),
            cons: Box::new(Stmt::Block(BlockStmt {
                span: DUMMY_SP,
                stmts: vec![yield_stmt],
            })),
            alt: None,
        });
        let body = BlockStmt {
            span: DUMMY_SP,
            stmts: vec![conditional_yield],
        };
        let inner_try = TryStmt {
            span: DUMMY_SP,
            block: body,
            handler: None,
            finalizer: Some(BlockStmt {
                span: DUMMY_SP,
                stmts: vec![throw_iterator_error],
            }),
        }
        .into();
        BlockStmt {
            span: DUMMY_SP,
            stmts: vec![inner_try],
        }
    };
    let try_stmt = TryStmt {
        span: s.span,
        block: try_body,
        handler: Some(catch_clause),
        finalizer: Some(finally_block),
    };
    let stmts = vec![
        VarDecl {
            span: DUMMY_SP,
            kind: VarDeclKind::Var,
            declare: false,
            decls: vec![
                VarDeclarator {
                    span: DUMMY_SP,
                    name: iterator_abrupt_completion.into(),
                    init: Some(false.into()),
                    definite: false,
                },
                VarDeclarator {
                    span: DUMMY_SP,
                    name: did_iteration_error.into(),
                    init: Some(false.into()),
                    definite: false,
                },
                VarDeclarator {
                    span: DUMMY_SP,
                    name: iterator_error.into(),
                    init: None,
                    definite: false,
                },
            ],
        }
        .into(),
        try_stmt.into(),
    ];
    *stmt = Stmt::Block(BlockStmt {
        span: s.span,
        stmts,
    })
}