use crate::syntax::{
    ast::{
        node::{
            declaration::{BindingPatternTypeArray, BindingPatternTypeObject},
            object::PropertyName,
            Block, Catch, Declaration, DeclarationList, Finally, Identifier, Try,
        },
        Const,
    },
    parser::tests::{check_invalid, check_parser},
};
use boa_interner::Interner;

#[test]
fn check_inline_with_empty_try_catch() {
    let mut interner = Interner::default();
    check_parser(
        "try { } catch(e) {}",
        vec![Try::new(
            vec![],
            Some(Catch::new(
                Declaration::new_with_identifier(interner.get_or_intern_static("e"), None),
                vec![],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_var_decl_inside_try() {
    let mut interner = Interner::default();
    check_parser(
        "try { var x = 1; } catch(e) {}",
        vec![Try::new(
            vec![DeclarationList::Var(
                vec![Declaration::new_with_identifier(
                    interner.get_or_intern_static("x"),
                    Some(Const::from(1).into()),
                )]
                .into(),
            )
            .into()],
            Some(Catch::new(
                Declaration::new_with_identifier(interner.get_or_intern_static("e"), None),
                vec![],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_var_decl_inside_catch() {
    let mut interner = Interner::default();
    check_parser(
        "try { var x = 1; } catch(e) { var x = 1; }",
        vec![Try::new(
            vec![DeclarationList::Var(
                vec![Declaration::new_with_identifier(
                    interner.get_or_intern_static("x"),
                    Some(Const::from(1).into()),
                )]
                .into(),
            )
            .into()],
            Some(Catch::new(
                Declaration::new_with_identifier(interner.get_or_intern_static("e"), None),
                vec![DeclarationList::Var(
                    vec![Declaration::new_with_identifier(
                        interner.get_or_intern_static("x"),
                        Some(Const::from(1).into()),
                    )]
                    .into(),
                )
                .into()],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_empty_try_catch_finally() {
    let mut interner = Interner::default();
    check_parser(
        "try {} catch(e) {} finally {}",
        vec![Try::new(
            vec![],
            Some(Catch::new(
                Declaration::new_with_identifier(interner.get_or_intern_static("e"), None),
                vec![],
            )),
            Some(Finally::from(vec![])),
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_empty_try_finally() {
    check_parser(
        "try {} finally {}",
        vec![Try::new(vec![], None, Some(Finally::from(vec![]))).into()],
        Interner::default(),
    );
}

#[test]
fn check_inline_with_empty_try_var_decl_in_finally() {
    let mut interner = Interner::default();
    check_parser(
        "try {} finally { var x = 1; }",
        vec![Try::new(
            vec![],
            None,
            Some(Finally::from(vec![DeclarationList::Var(
                vec![Declaration::new_with_identifier(
                    interner.get_or_intern_static("x"),
                    Some(Const::from(1).into()),
                )]
                .into(),
            )
            .into()])),
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_empty_try_paramless_catch() {
    let mut interner = Interner::default();
    check_parser(
        "try {} catch { var x = 1; }",
        vec![Try::new(
            Block::from(vec![]),
            Some(Catch::new::<_, Declaration, _>(
                None,
                vec![DeclarationList::Var(
                    vec![Declaration::new_with_identifier(
                        interner.get_or_intern_static("x"),
                        Some(Const::from(1).into()),
                    )]
                    .into(),
                )
                .into()],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_binding_pattern_object() {
    let mut interner = Interner::default();
    let a = interner.get_or_intern_static("a");
    check_parser(
        "try {} catch ({ a, b: c }) {}",
        vec![Try::new(
            Block::from(vec![]),
            Some(Catch::new::<_, Declaration, _>(
                Some(Declaration::new_with_object_pattern(
                    vec![
                        BindingPatternTypeObject::SingleName {
                            ident: a,
                            property_name: PropertyName::Literal(a),
                            default_init: None,
                        },
                        BindingPatternTypeObject::SingleName {
                            ident: interner.get_or_intern_static("c"),
                            property_name: PropertyName::Literal(
                                interner.get_or_intern_static("b"),
                            ),
                            default_init: None,
                        },
                    ],
                    None,
                )),
                vec![],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_with_binding_pattern_array() {
    let mut interner = Interner::default();
    check_parser(
        "try {} catch ([a, b]) {}",
        vec![Try::new(
            Block::from(vec![]),
            Some(Catch::new::<_, Declaration, _>(
                Some(Declaration::new_with_array_pattern(
                    vec![
                        BindingPatternTypeArray::SingleName {
                            ident: interner.get_or_intern_static("a"),
                            default_init: None,
                        },
                        BindingPatternTypeArray::SingleName {
                            ident: interner.get_or_intern_static("b"),
                            default_init: None,
                        },
                    ],
                    None,
                )),
                vec![],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_catch_with_var_redeclaration() {
    let mut interner = Interner::default();
    check_parser(
        "try {} catch(e) { var e = 'oh' }",
        vec![Try::new(
            Block::from(vec![]),
            Some(Catch::new::<_, Declaration, _>(
                Some(Declaration::new_with_identifier(
                    Identifier::new(interner.get_or_intern_static("e")),
                    None,
                )),
                vec![DeclarationList::Var(
                    vec![Declaration::new_with_identifier(
                        interner.get_or_intern_static("e"),
                        Some(Const::from(interner.get_or_intern_static("oh")).into()),
                    )]
                    .into(),
                )
                .into()],
            )),
            None,
        )
        .into()],
        interner,
    );
}

#[test]
fn check_inline_invalid_catch() {
    check_invalid("try {} catch");
}

#[test]
fn check_inline_invalid_catch_without_closing_paren() {
    check_invalid("try {} catch(e {}");
}

#[test]
fn check_inline_invalid_catch_parameter() {
    check_invalid("try {} catch(1) {}");
}

#[test]
fn check_invalid_try_no_catch_finally() {
    check_invalid("try {} let a = 10;");
}

#[test]
fn check_invalid_catch_with_empty_paren() {
    check_invalid("try {} catch() {}");
}

#[test]
fn check_invalid_catch_with_duplicate_params() {
    check_invalid("try {} catch({ a, b: a }) {}");
}

#[test]
fn check_invalid_catch_with_lexical_redeclaration() {
    check_invalid("try {} catch(e) { let e = 'oh' }");
}
