#![warn(clippy::return_and_then)]

fn main() {
    fn test_opt_block(opt: Option<i32>) -> Option<i32> {
        let n = opt?;
        //~^ return_and_then
        let mut ret = n + 1;
        ret += n;
        if n > 1 { Some(ret) } else { None }
    }

    fn test_opt_func(opt: Option<i32>) -> Option<i32> {
        let n = opt?;
        test_opt_block(Some(n))
        //~^ return_and_then
    }

    fn test_call_chain() -> Option<i32> {
        let n = gen_option(1)?;
        test_opt_block(Some(n))
        //~^ return_and_then
    }

    fn test_res_block(opt: Result<i32, i32>) -> Result<i32, i32> {
        let n = opt?;
        if n > 1 { Ok(n + 1) } else { Err(n) }
        //~^ return_and_then
    }

    fn test_res_func(opt: Result<i32, i32>) -> Result<i32, i32> {
        let n = opt?;
        test_res_block(Ok(n))
        //~^ return_and_then
    }

    fn test_ref_only() -> Option<i32> {
        // ref: empty string
        let x = Some("")?;
        if x.len() > 2 { Some(3) } else { None }
        //~^ return_and_then
    }

    fn test_tmp_only() -> Option<i32> {
        // unused temporary: vec![1, 2, 4]
        let x = Some(match (vec![1, 2, 3], vec![1, 2, 4]) {
            //~^ return_and_then
            (a, _) if a.len() > 1 => a,
            (_, b) => b,
        })?;
        if x.len() > 2 { Some(3) } else { None }
    }

    // should not lint
    fn test_tmp_ref() -> Option<String> {
        String::from("<BOOM>")
            .strip_prefix("<")
            .and_then(|s| s.strip_suffix(">").map(String::from))
    }

    // should not lint
    fn test_unconsumed_tmp() -> Option<i32> {
        [1, 2, 3]
            .iter()
            .map(|x| x + 1)
            .collect::<Vec<_>>() // temporary Vec created here
            .as_slice() // creates temporary slice
            .first() // creates temporary reference
            .and_then(|x| test_opt_block(Some(*x)))
    }

    fn in_closure() -> bool {
        let _ = || {
            let x = Some("")?;
            if x.len() > 2 { Some(3) } else { None }
            //~^ return_and_then
        };
        true
    }

    fn with_return(shortcut: bool) -> Option<i32> {
        if shortcut {
            return {
                let x = Some("")?;
                if x.len() > 2 { Some(3) } else { None }
            };
            //~^ return_and_then
        };
        None
    }

    fn with_return_multiline(shortcut: bool) -> Option<i32> {
        if shortcut {
            return {
                let mut x = Some("")?;
                let x = format!("{x}.");
                if x.len() > 2 { Some(3) } else { None }
            };
            //~^^^^ return_and_then
        };
        None
    }

    #[expect(clippy::diverging_sub_expression)]
    fn with_return_in_expression() -> Option<i32> {
        _ = (
            return {
                let x = Some("")?;
                if x.len() > 2 { Some(3) } else { None }
            },
            //~^ return_and_then
            10,
        );
    }

    fn inside_if(a: bool, i: Option<u32>) -> Option<u32> {
        if a {
            let i = i?;
            if i > 3 { Some(i) } else { None }
            //~^ return_and_then
        } else {
            Some(42)
        }
    }

    fn inside_match(a: u32, i: Option<u32>) -> Option<u32> {
        match a {
            1 | 2 => {
                let i = i?;
                if i > 3 { Some(i) } else { None }
            },
            //~^ return_and_then
            3 | 4 => Some(42),
            _ => None,
        }
    }

    fn inside_match_and_block_and_if(a: u32, i: Option<u32>) -> Option<u32> {
        match a {
            1 | 2 => {
                let a = a * 3;
                if a.is_multiple_of(2) {
                    let i = i?;
                    if i > 3 { Some(i) } else { None }
                    //~^ return_and_then
                } else {
                    Some(10)
                }
            },
            3 | 4 => Some(42),
            _ => None,
        }
    }

    #[expect(clippy::never_loop)]
    fn with_break(i: Option<u32>) -> Option<u32> {
        match i {
            Some(1) => loop {
                break ({
                    let i = i?;
                    if i > 3 { Some(i) } else { None }
                });
                //~^ return_and_then
            },
            Some(2) => 'foo: loop {
                loop {
                    break 'foo ({
                        let i = i?;
                        if i > 3 { Some(i) } else { None }
                    });
                    //~^ return_and_then
                }
            },
            Some(3) => 'bar: {
                break 'bar ({
                    let i = i?;
                    if i > 3 { Some(i) } else { None }
                });
                //~^ return_and_then
            },
            Some(4) => 'baz: loop {
                _ = loop {
                    break i.and_then(|i| if i > 3 { Some(i) } else { None });
                };
            },
            _ => None,
        }
    }
}

fn gen_option(n: i32) -> Option<i32> {
    Some(n)
}

mod issue14781 {
    fn foo(_: &str, _: (u32, u32)) -> Result<(u32, u32), ()> {
        Ok((1, 1))
    }

    fn bug(_: Option<&str>) -> Result<(), ()> {
        let year: Option<&str> = None;
        let month: Option<&str> = None;
        let day: Option<&str> = None;

        let _day = if let (Some(year), Some(month)) = (year, month) {
            day.and_then(|day| foo(day, (1, 31)).ok())
        } else {
            None
        };

        Ok(())
    }
}

mod issue15111 {
    #[derive(Debug)]
    struct EvenOdd {
        even: Option<u32>,
        odd: Option<u32>,
    }

    impl EvenOdd {
        fn new(i: Option<u32>) -> Self {
            Self {
                even: i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }),
                odd: i.and_then(|i| if i.is_multiple_of(2) { None } else { Some(i) }),
            }
        }
    }

    fn with_if_let(i: Option<u32>) -> u32 {
        if let Some(x) = i.and_then(|i| if i.is_multiple_of(2) { Some(i) } else { None }) {
            x
        } else {
            std::hint::black_box(0)
        }
    }

    fn main() {
        let _ = EvenOdd::new(Some(2));
    }
}

mod issue14927 {
    use std::path::Path;
    struct A {
        pub func: fn(check: bool, a: &Path, b: Option<&Path>),
    }
    const MY_A: A = A {
        func: |check, a, b| {
            if check {
                let _ = ();
            } else if let Some(parent) = b.and_then(|p| p.parent()) {
                let _ = ();
            }
        },
    };
}
