use super::*;

#[test]
fn parses_command_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "print");
                    assert!(matches!(words[0], ShellWord::BareWord { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected command statement"),
    }
}

#[test]
fn parses_command_dotdot_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotdot-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "setParent");
                    assert!(matches!(
                        words[0],
                        ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == ".."
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotdot_after_flag_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotdot-flag-arg.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == ".."
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotdot_without_whitespace() {
    let parse = parse_source("setParent..;");
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "setParent");
                    assert!(matches!(
                        words[0],
                        ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == ".."
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn keeps_quoted_dotdot_as_quoted_string() {
    let parse = parse_source(r#"setParent "..";"#);
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(
                        words[0],
                        ShellWord::QuotedString { ref text, .. } if parse.source_slice(*text) == "\"..\""
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn keeps_no_whitespace_ident_lparen_as_function_stmt() {
    let parse = parse_source("doItDRA();");
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::Function {
                    head_range, args, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "doItDRA");
                    assert!(args.is_empty());
                }
                _ => panic!("expected function invoke"),
            },
            _ => panic!("expected expression statement"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_function_stmt_across_line_break_and_following_keyword() {
    let parse = parse_source("foo(\n    \"arg\"\n);\nif ($ready) {\n    print $ready;\n}\n");
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::Function {
                    head_range, args, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "foo");
                    assert_eq!(args.len(), 1);
                }
                _ => panic!("expected function invoke"),
            },
            _ => panic!("expected expression statement"),
        },
        _ => panic!("expected statement"),
    }

    assert!(matches!(
        parse.syntax.items[1],
        Item::Stmt(ref stmt) if matches!(&**stmt, Stmt::If { .. })
    ));
}

#[test]
fn parses_function_stmt_spaced_lparen_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/function-stmt-spaced-lparen.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::Function {
                    head_range, args, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "tmBuildSet");
                    assert_eq!(args.len(), 2);
                }
                _ => panic!("expected function invoke"),
            },
            _ => panic!("expected expression statement"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_leading_grouped_arg_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-leading-grouped-arg.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "renameAttr");
                    assert!(matches!(
                        words[0],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Binary { .. })
                    ));
                    assert!(matches!(words[1], ShellWord::Variable { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_numeric_arg_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-numeric-arg.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(words[1], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[2],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == "0"
                    ));
                    assert!(matches!(words[3], ShellWord::Variable { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_signed_numeric_arg_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-signed-numeric-arg.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(
                        words[3],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == "-10"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_leading_dot_float_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-leading-dot-float.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == ".7"
                    ));
                    assert!(matches!(words[2], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[3],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == ".001"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_trailing_dot_float_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-trailing-dot-float.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike { words, .. } => {
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(words[1], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[2],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == "-1000."
                    ));
                    assert!(matches!(words[3], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[4],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == "1000."
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_grouped_subtraction_call_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/expressions/grouped-subtraction-call.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::VarDecl { decl, .. } => match &decl.declarators[0].initializer {
                Some(Expr::Invoke(invoke)) => match &invoke.surface {
                    InvokeSurface::Function { args, .. } => {
                        assert!(matches!(
                            args[1],
                            Expr::Binary {
                                op: BinaryOp::Sub,
                                ..
                            }
                        ));
                    }
                    _ => panic!("expected function invoke"),
                },
                _ => panic!("expected invoke initializer"),
            },
            _ => panic!("expected variable declaration"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_spaced_flag_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-spaced-flag.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::VarDecl { decl, .. } => match &decl.declarators[0].initializer {
                Some(Expr::Invoke(invoke)) => match &invoke.surface {
                    InvokeSurface::ShellLike {
                        head_range, words, ..
                    } => {
                        assert_eq!(parse.source_slice(*head_range), "optionVar");
                        assert!(matches!(
                            words[0],
                            ShellWord::Flag { ref text, .. } if parse.source_slice(*text) == "- q"
                        ));
                        assert!(matches!(
                            words[1],
                            ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "LayoutPreviewResolution"
                        ));
                    }
                    _ => panic!("expected shell-like invoke"),
                },
                _ => panic!("expected invoke initializer"),
            },
            _ => panic!("expected variable declaration"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_multiline_grouped_args_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-multiline-grouped-args.mel"
    ));
    assert!(parse.errors.is_empty());
    assert_eq!(parse.syntax.items.len(), 2);

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "connectAttr");
                    assert_eq!(words.len(), 2);
                    assert!(matches!(words[0], ShellWord::GroupedExpr { .. }));
                    assert!(matches!(words[1], ShellWord::GroupedExpr { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }

    match &parse.syntax.items[1] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "setAttr");
                    assert!(matches!(words[0], ShellWord::GroupedExpr { .. }));
                    assert!(
                        matches!(words[1], ShellWord::Flag { ref text, .. } if parse.source_slice(*text) == "-type")
                    );
                    assert!(matches!(
                        words[2],
                        ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == "double3"
                    ));
                    assert!(matches!(words[3], ShellWord::GroupedExpr { .. }));
                    assert!(matches!(words[4], ShellWord::GroupedExpr { .. }));
                    assert!(matches!(words[5], ShellWord::GroupedExpr { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_point_constraint_brace_list_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-point-constraint-brace-list.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "applyPointConstraintArgs");
                    assert!(matches!(
                        words[0],
                        ShellWord::NumericLiteral { ref text, .. } if parse.source_slice(*text) == "2"
                    ));
                    assert!(matches!(
                        words[1],
                        ShellWord::BraceList {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::ArrayLiteral { elements, .. } if elements.len() == 10)
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_orient_constraint_brace_list_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-orient-constraint-brace-list.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "applyOrientConstraintArgs");
                    match &words[1] {
                        ShellWord::BraceList { expr, .. } => match &**expr {
                            Expr::ArrayLiteral { elements, .. } => {
                                assert!(matches!(
                                    elements[0],
                                    Expr::String { ref text, .. } if parse.source_slice(*text) == "\"1\""
                                ));
                                assert!(matches!(
                                    elements[7],
                                    Expr::String { ref text, .. } if parse.source_slice(*text) == "\"8\""
                                ));
                                assert!(matches!(
                                    elements[8],
                                    Expr::String { ref text, .. } if parse.source_slice(*text) == "\"\""
                                ));
                            }
                            _ => panic!("expected array literal"),
                        },
                        _ => panic!("expected brace-list shell word"),
                    }
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_capture_vector_literal_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-capture-vector-literal.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::VarDecl { decl, .. } => match &decl.declarators[0].initializer {
                Some(Expr::Invoke(invoke)) => match &invoke.surface {
                    InvokeSurface::ShellLike {
                        head_range,
                        words,
                        captured,
                        ..
                    } => {
                        assert_eq!(parse.source_slice(*head_range), "hsv_to_rgb");
                        assert!(*captured);
                        assert!(matches!(
                            words[0],
                            ShellWord::VectorLiteral {
                                ref expr,
                                ..
                            } if matches!(&**expr, Expr::VectorLiteral { elements, .. } if elements.len() == 3)
                        ));
                    }
                    _ => panic!("expected shell-like invoke"),
                },
                _ => panic!("expected invoke initializer"),
            },
            _ => panic!("expected variable declaration"),
        },
        _ => panic!("expected statement"),
    }

    match &parse.syntax.items[1] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "text");
                    assert!(matches!(
                        words[2],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::ComponentAccess { .. })
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotted_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotted-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "setDrivenKeyframe");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "N_arm_01.rotateX"
                    ));
                    assert!(matches!(
                        words[2],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "N_arm_01_H.rotateX"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotted_indexed_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotted-indexed-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "connectAttr");
                    assert!(matches!(
                        words[0],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "foo.worldMatrix[0]"
                    ));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "bar.inputWorldMatrix"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotted_variable_indexed_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotted-variable-indexed-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "connectAttr");
                    assert!(matches!(
                        words[0],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Binary { .. })
                    ));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "LayerRegistry.layerSlot[$index]"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_dotted_global_attr_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-dotted-global-attr.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "getAttr");
                    assert!(matches!(
                        words[0],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "defaultRenderGlobals.hyperShadeBinList"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_pipe_dag_path_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-pipe-dag-path.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "select");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "Null|Spine_00|Tail_00"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_pipe_wildcard_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-pipe-wildcard-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "select");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == "*|_x005"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_absolute_plug_path_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-absolute-plug-path.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "defaultNavigation");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(
                        matches!(words[1], ShellWord::BareWord { ref text, .. } if parse.source_slice(*text) == "shaderNodePreview1")
                    );
                    assert!(matches!(words[2], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[3],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "|geoPreview1|geoPreviewShape1.instObjGroups[0]"
                    ));
                    assert!(matches!(words[4], ShellWord::Flag { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_namespace_pipe_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-namespace-pipe-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "select");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::BareWord { ref text, .. }
                            if parse.source_slice(*text) == "ns:root|ns:spine|ns:ctrl"
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_leading_colon_bareword_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-leading-colon-bareword.mel"
    ));
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::VarDecl { decl, .. } => match &decl.declarators[0].initializer {
                Some(Expr::Invoke(invoke)) => match &invoke.surface {
                    InvokeSurface::ShellLike {
                        head_range, words, ..
                    } => {
                        assert_eq!(parse.source_slice(*head_range), "camera");
                        assert!(matches!(words[0], ShellWord::Flag { .. }));
                        assert!(matches!(
                            words[1],
                            ShellWord::BareWord { ref text, .. }
                                if parse.source_slice(*text) == ":previewViewportCamera"
                        ));
                    }
                    _ => panic!("expected shell-like invoke"),
                },
                _ => panic!("expected invoke initializer"),
            },
            _ => panic!("expected variable declaration"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_command_grouped_args_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-grouped-args.mel"
    ));
    assert!(parse.errors.is_empty());
    assert_eq!(parse.syntax.items.len(), 2);

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "iconTextButton");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(words[1], ShellWord::QuotedString { .. }));
                    assert!(matches!(
                        words[5],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Binary { .. })
                    ));
                    assert!(matches!(
                        words[7],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Binary { .. })
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected command statement"),
    }

    match &parse.syntax.items[1] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "menuItem");
                    assert!(matches!(
                        words[1],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Binary { .. })
                    ));
                    assert!(matches!(
                        words[3],
                        ShellWord::Variable {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::MemberAccess { member, .. } if parse.source_slice(*member) == "name")
                    ));
                    assert!(matches!(words[5], ShellWord::Capture { .. }));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected command statement"),
    }
}

#[test]
fn parses_command_capture_grouped_function_call_fixture() {
    let parse = parse_source(include_str!(
        "../../../../../tests/corpus/parser/statements/command-capture-grouped-function-call.mel"
    ));
    assert!(parse.errors.is_empty());
    assert_eq!(parse.syntax.items.len(), 2);

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::VarDecl { decl, .. } => match &decl.declarators[0].initializer {
                Some(Expr::Unary { expr, .. }) => match &**expr {
                    Expr::Invoke(invoke) => match &invoke.surface {
                        InvokeSurface::ShellLike {
                            head_range,
                            words,
                            captured,
                            ..
                        } => {
                            assert_eq!(parse.source_slice(*head_range), "optionVar");
                            assert!(*captured);
                            assert!(matches!(words[0], ShellWord::Flag { .. }));
                            assert!(matches!(
                                words[1],
                                ShellWord::GroupedExpr {
                                    ref expr,
                                    ..
                                } if matches!(&**expr, Expr::Invoke(_))
                            ));
                        }
                        _ => panic!("expected shell-like capture"),
                    },
                    _ => panic!("expected invoke under unary expression"),
                },
                _ => panic!("expected unary initializer"),
            },
            _ => panic!("expected variable declaration"),
        },
        _ => panic!("expected statement"),
    }

    match &parse.syntax.items[1] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Invoke(invoke),
                ..
            } => match &invoke.surface {
                InvokeSurface::ShellLike {
                    head_range, words, ..
                } => {
                    assert_eq!(parse.source_slice(*head_range), "optionVar");
                    assert!(matches!(words[0], ShellWord::Flag { .. }));
                    assert!(matches!(
                        words[1],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Invoke(_))
                    ));
                    assert!(matches!(
                        words[2],
                        ShellWord::GroupedExpr {
                            ref expr,
                            ..
                        } if matches!(&**expr, Expr::Invoke(_))
                    ));
                }
                _ => panic!("expected shell-like invoke"),
            },
            _ => panic!("expected command expression"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_index_and_add_assign() {
    let parse = parse_source("$items[$i] += 1;");
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr: Expr::Assign { op, lhs, .. },
                ..
            } => {
                assert!(matches!(op, AssignOp::AddAssign));
                assert!(matches!(**lhs, Expr::Index { .. }));
            }
            _ => panic!("expected add-assign statement"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_operator_precedence() {
    let parse = parse_source("$value = 1 + 2 * 3;");
    assert!(parse.errors.is_empty());

    match &parse.syntax.items[0] {
        Item::Stmt(stmt) => match &**stmt {
            Stmt::Expr {
                expr:
                    Expr::Assign {
                        rhs,
                        op: AssignOp::Assign,
                        ..
                    },
                ..
            } => match &**rhs {
                Expr::Binary {
                    op: BinaryOp::Add,
                    rhs,
                    ..
                } => {
                    assert!(matches!(
                        **rhs,
                        Expr::Binary {
                            op: BinaryOp::Mul,
                            ..
                        }
                    ));
                }
                _ => panic!("expected additive expression"),
            },
            _ => panic!("expected assignment"),
        },
        _ => panic!("expected statement"),
    }
}

#[test]
fn parses_shell_variable_member_index_chain_inline() {
    let parse = parse_source("setAttr $node.attr[$index] 1;");
    assert!(parse.errors.is_empty());

    let Item::Stmt(stmt) = &parse.syntax.items[0] else {
        panic!("expected command statement");
    };
    let Stmt::Expr {
        expr: Expr::Invoke(invoke),
        ..
    } = &**stmt
    else {
        panic!("expected invoke expression");
    };
    let InvokeSurface::ShellLike { words, .. } = &invoke.surface else {
        panic!("expected shell-like invoke");
    };
    let ShellWord::Variable { expr, .. } = &words[0] else {
        panic!("expected variable shell word");
    };

    match &**expr {
        Expr::Index { target, index, .. } => {
            assert!(matches!(
                &**target,
                Expr::MemberAccess { member, .. } if parse.source_slice(*member) == "attr"
            ));
            assert!(matches!(
                &**index,
                Expr::Ident { name_range, .. } if parse.source_slice(*name_range) == "$index"
            ));
        }
        _ => panic!("expected indexed member access"),
    }
}

#[test]
fn parses_shell_path_like_bareword_with_namespace_member_and_index() {
    let parse = parse_source("select ns:node.attr[3];");
    assert!(parse.errors.is_empty());

    let Item::Stmt(stmt) = &parse.syntax.items[0] else {
        panic!("expected command statement");
    };
    let Stmt::Expr {
        expr: Expr::Invoke(invoke),
        ..
    } = &**stmt
    else {
        panic!("expected invoke expression");
    };
    let InvokeSurface::ShellLike { words, .. } = &invoke.surface else {
        panic!("expected shell-like invoke");
    };
    let ShellWord::BareWord { text, .. } = &words[0] else {
        panic!("expected bareword shell word");
    };

    assert_eq!(parse.source_slice(*text), "ns:node.attr[3]");
}
