#[macro_export]
macro_rules! regexm {
    (
        match $str:tt {captures($pattern:expr) => |$caps:ident| $expr:expr, $($rest:tt)*}
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $pattern,
            first_expr = {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            },
            result = unknown,
        }
    };

    (
        match $str:tt {captures($pattern:expr) => |$caps:ident| $expr:block $($rest:tt)*}
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $pattern,
            first_expr = {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            },
            result = unknown,
        }
    };

    (
        match $str:tt {$pattern:expr => $expr:expr, $($rest:tt)*}
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $pattern,
            first_expr = $expr,
            result = unknown,
        }
    };

    (
        match $str:tt {$pattern:expr => $expr:block $($rest:tt)*}
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $pattern,
            first_expr = $expr,
            result = unknown,
        }
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __regexm {
    (
        tokens = [captures($default:pat) => |$caps:ident| $default_expr:expr$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $($result)* else {
            let closure = |$caps: regex::Captures| $default_expr;
            closure(regex::Regex::new($default).unwrap().captures($str).unwrap())
        };
    };

    (
        tokens = [captures($default:pat) => |$caps:ident| $default_expr:block$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $($result)* else {
            let closure = |$caps: regex::Captures| $default_expr;
            closure(regex::Regex::new($default).unwrap().captures($str).unwrap())
        };
    };

    (
        tokens = [$default:pat => $default_expr:expr$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $($result)* else {$default_expr};
    };

    (
        tokens = [$default:pat => $default_expr:block$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $($result)* else {$default_expr};
    };

    (
        tokens = [],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $($result)*
    };

    (
        tokens = [captures($default:pat) => |$caps:ident| $default_expr:expr$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        if regex::Regex::new($first_pattern).unwrap().is_match($str) {
            $first_expr
        } else {
            let closure = |$caps: regex::Captures| $default_expr;
            closure(regex::Regex::new($default).unwrap().captures($str).unwrap())
        };
    };

    (
        tokens = [captures($default:pat) => |$caps:ident| $default_expr:block$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        if regex::Regex::new($first_pattern).unwrap().is_match($str) {
            $first_expr
        } else {
            let closure = |$caps: regex::Captures| $default_expr;
            closure(regex::Regex::new($default).unwrap().captures($str).unwrap())
        };
    };

    (
        tokens = [$default:pat => $default_expr:expr$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        if regex::Regex::new($first_pattern).unwrap().is_match($str) {
            $first_expr
        } else {$default_expr};
    };

    (
        tokens = [$default:pat => $default_expr:block$(,)?],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_token = $first_tokens:expr,
        result = unknown,
    ) => {
        if regex::Regex::new($first_pattern).unwrap().is_match($str) {
            $first_tokens
        } else {$default_expr};
    };

    (
        tokens = [captures($pattern:expr) => |$caps:ident| $expr:expr, $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [if regex::Regex::new($first_pattern).unwrap().is_match($str) {
                $first_expr
            } else if regex::Regex::new($pattern).unwrap().is_match($str) {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            }],
        }
    };

    (
        tokens = [captures($pattern:expr) => |$caps:ident| $expr:block $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [if regex::Regex::new($first_pattern).unwrap().is_match($str) {
                $first_expr
            } else if regex::Regex::new($pattern).unwrap().is_match($str) {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            }],
        }
    };

    (
        tokens = [$pattern:expr => $expr:expr, $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [if regex::Regex::new($first_pattern).unwrap().is_match($str) {
                $first_expr
            } else if regex::Regex::new($pattern).unwrap().is_match($str) {
                $expr
            }],
        }
    };

    (
        tokens = [$pattern:expr => $expr:block $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = unknown,
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [if regex::Regex::new($first_pattern).unwrap().is_match($str) {
                $first_expr
            } else if regex::Regex::new($pattern).unwrap().is_match($str) {
                $expr
            }],
        }
    };

    (
        tokens = [captures($pattern:expr) => |$caps:ident| $expr:expr, $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [$($result)* else if regex::Regex::new($pattern).unwrap().is_match($str) {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            }],
        }
    };

    (
        tokens = [captures($pattern:expr) => |$caps:ident| $expr:block $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [$($result)* else if regex::Regex::new($pattern).unwrap().is_match($str) {
                let closure = |$caps: regex::Captures| $expr;
                closure(regex::Regex::new($pattern).unwrap().captures($str).unwrap())
            }],
        }
    };

    (
        tokens = [$pattern:expr => $expr:expr, $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [$($result)* else if regex::Regex::new($pattern).unwrap().is_match($str) {
                $expr
            }],
        }
    };

    (
        tokens = [$pattern:expr => $expr:block $($rest:tt)*],
        str = $str:expr,
        first_pattern = $first_pattern:expr,
        first_expr = $first_expr:expr,
        result = [$($result:tt)*],
    ) => {
        $crate::__regexm! {
            tokens = [$($rest)*],
            str = $str,
            first_pattern = $first_pattern,
            first_expr = $first_expr,
            result = [$($result)* else if regex::Regex::new($pattern).unwrap().is_match($str) {
                $expr
            }],
        }
    };
}

#[cfg(test)]
mod test {
    #[test]
    fn test_match_3_or_more_pattern() {
        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => assert!(true),
            r"^\d{4}-\d{2}$" => assert!(false),
            _ => assert!(false),
        });

        regexm!(match "foo" {
            r"^\d{4}-\d{2}-\d{2}$" => assert!(false),
            r"^\d{4}-\d{2}$" => assert!(false),
            _ => assert!(true),
        });

        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => assert!(true),
            r"^\d{4}-\d{2}$" => assert!(false),
            r"^\d{4}$" => assert!(false),
            _ => assert!(false),
        });
    }

    #[test]
    fn test_match_3_or_more_pattern_block() {
        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                assert!(true);
                assert!(true);
            }
            r"^\d{4}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });

        regexm!(match "foo" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            r"^\d{4}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            _ => {
                assert!(true);
                assert!(true)
            }
        });

        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                assert!(true);
                assert!(true);
            }
            r"^\d{4}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            r"^\d{4}$" => {
                assert!(false);
                assert!(false);
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });
    }

    #[test]
    fn test_match_2_or_less_pattern() {
        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => assert!(true),
            _ => assert!(false),
        });

        regexm!(match "foo" {
            r"^\d{4}-\d{2}$" => assert!(false),
            _ => assert!(true),
        });
    }

    #[test]
    fn test_match_2_or_less_pattern_block() {
        regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                assert!(true);
                assert!(true);
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });

        regexm!(match "foo" {
            r"^\d{4}-\d{2}$" => {
                assert!(false);
                assert!(false)
            }
            _ => {
                assert!(true);
                assert!(true);
            }
        });
    }

    #[test]
    fn test_let_match_3_or_more_pattern() {
        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => "yyyy-mm-dd",
            r"^\d{4}-\d{2}$" => "yyyy-mm",
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");

        let foo = regexm!(match "foo" {
            r"^\d{4}-\d{2}-\d{2}$" => "yyyy-mm-dd",
            r"^\d{4}-\d{2}$" => "yyyy-mm",
            _ => "default",
        });
        assert_eq!(foo, "default");

        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => "yyyy-mm-dd",
            r"^\d{4}-\d{2}$" => "yyyy-mm",
            r"^\d{4}$" => "yyyy",
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");
    }

    #[test]
    fn test_let_match_3_or_more_pattern_block() {
        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                let bar = "yyyy-mm-dd";
                bar
            }
            r"^\d{4}-\d{2}$" => {
                let bar = "yyyy-mm";
                bar
            }
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");

        let foo = regexm!(match "foo" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                let bar = "yyyy-mm-dd";
                bar
            }
            r"^\d{4}-\d{2}$" => {
                let bar = "yyyy-mm";
                bar
            }
            _ => "default",
        });
        assert_eq!(foo, "default");

        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                let bar = "yyyy-mm-dd";
                bar
            }
            r"^\d{4}-\d{2}$" => {
                let bar = "yyyy-mm";
                bar
            }
            r"^\d{4}$" => {
                let bar = "yyyy";
                bar
            }
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");
    }

    #[test]
    fn test_let_match_2_or_less_pattern() {
        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => "yyyy-mm-dd",
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");

        let foo = regexm!(match "foo" {
            r"^\d{4}-\d{2}$" => "yyyy-mm",
            _ => "default",
        });
        assert_eq!(foo, "default");
    }

    #[test]
    fn test_let_match_2_or_less_pattern_block() {
        let foo = regexm!(match "2021-01-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                let bar = "yyyy-mm-dd";
                bar
            }
            _ => "default",
        });
        assert_eq!(foo, "yyyy-mm-dd");

        let foo = regexm!(match "foo" {
            r"^\d{4}-\d{2}$" => {
                let bar = "yyyy-mm";
                bar
            }
            _ => "default",
        });
        assert_eq!(foo, "default");
    }

    #[test]
    fn test_capture_groups_match_3_or_more_pattern() {
        regexm!(match "2021-01-01" {
            captures(r"^(\d{4})-\d{2}-\d{2}$") =>
                |caps| assert_eq!(caps.get(1).map_or("", |m| m.as_str()), "2021"),
            r"^\d{4}-\d{2}$" => assert!(false),
            _ => assert!(false),
        });

        regexm!(match "2021-01" {
            r"^\d{4}-\d{2}-\d{2}$" => assert!(false),
            captures(r"^(\d{4})-\d{2}$") =>
                |caps| assert_eq!(caps.get(1).map_or("", |m| m.as_str()), "2021"),
            r"^\d{4}$" => assert!(false),
            _ => assert!(false),
        });
    }

    #[test]
    fn test_capture_groups_match_3_or_more_pattern_block() {
        regexm!(match "2021-01-01" {
            captures(r"^(\d{4})-\d{2}-\d{2}$") => |caps| {
                let year = caps.get(1).map_or("", |m| m.as_str());
                assert_eq!(year, "2021")
            }
            r"^\d{4}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });

        regexm!(match "2021-01" {
            r"^\d{4}-\d{2}-\d{2}$" => {
                assert!(false);
                assert!(false);
            }
            captures(r"^(\d{4})-\d{2}$") => |caps| {
                let month = caps.get(1).map_or("", |m| m.as_str());
                assert_eq!(month, "2021")
            }
            r"^\d{4}$" => {
                assert!(false);
                assert!(false);
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });
    }

    #[test]
    fn test_capture_groups_match_2_or_less_pattern() {
        regexm!(match "2021-01-01" {
            captures(r"^(\d{4})-\d{2}-\d{2}$") =>
                |caps| assert_eq!(caps.get(1).map_or("", |m| m.as_str()), "2021"),
            _ => assert!(false),
        });

        regexm!(match "foo" {
            r"^\d{4}-\d{2}$" => assert!(false),
            _ => assert!(true),
        });
    }

    #[test]
    fn test_capture_groups_match_2_or_less_pattern_block() {
        regexm!(match "2021-01-01" {
            captures(r"^(\d{4})-\d{2}-\d{2}$") => |caps| {
                let year = caps.get(1).map_or("", |m| m.as_str());
                assert_eq!(year, "2021");
            }
            _ => {
                assert!(false);
                assert!(false)
            }
        });
    }
}
