#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)]
#![allow(
    dead_code,
    clippy::let_unit_value,
    clippy::useless_vec,
    clippy::double_ended_iterator_last
)]

fn main() {
    let vec = vec!["1".to_string(), "2".to_string(), "3".to_string()];

    let _: Option<String> = vec.iter().last().cloned();
    //~^ iter_overeager_cloned

    let _: Option<String> = vec.iter().chain(vec.iter()).next().cloned();
    //~^ iter_overeager_cloned

    let _: usize = vec.iter().filter(|x| x == &"2").count();
    //~^ redundant_iter_cloned

    let _: Vec<_> = vec.iter().take(2).cloned().collect();
    //~^ iter_overeager_cloned

    let _: Vec<_> = vec.iter().skip(2).cloned().collect();
    //~^ iter_overeager_cloned

    let _ = vec.iter().filter(|x| x == &"2").nth(2).cloned();
    //~^ iter_overeager_cloned

    let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))]
        //~^ iter_overeager_cloned
        .iter()
        .flatten().cloned();

    let _ = vec.iter().filter(|&x| x.starts_with('2')).cloned();
    //~^ iter_overeager_cloned

    let _ = vec.iter().find(|&x| x == "2").cloned();
    //~^ iter_overeager_cloned

    {
        let f = |x: &String| x.starts_with('2');
        let _ = vec.iter().filter(|&x| f(x)).cloned();
        //~^ iter_overeager_cloned
        let _ = vec.iter().find(|&x| f(x)).cloned();
        //~^ iter_overeager_cloned
    }

    {
        let vec: Vec<(String, String)> = vec![];
        let f = move |x: &(String, String)| x.0.starts_with('2');
        let _ = vec.iter().filter(|&x| f(x)).cloned();
        //~^ iter_overeager_cloned
        let _ = vec.iter().find(|&x| f(x)).cloned();
        //~^ iter_overeager_cloned
    }

    fn test_move<'a>(
        iter: impl Iterator<Item = &'a (&'a u32, String)> + 'a,
        target: String,
    ) -> impl Iterator<Item = (&'a u32, String)> + 'a {
        iter.filter(move |&&(&a, ref b)| a == 1 && b == &target).cloned()
        //~^ iter_overeager_cloned
    }

    {
        #[derive(Clone)]
        struct S<'a> {
            a: &'a u32,
            b: String,
        }

        fn bar<'a>(iter: impl Iterator<Item = &'a S<'a>> + 'a, target: String) -> impl Iterator<Item = S<'a>> + 'a {
            iter.filter(move |&S { a, b }| **a == 1 && b == &target).cloned()
            //~^ iter_overeager_cloned
        }
    }

    let _ = vec.iter().map(|x| x.len());
    //~^ redundant_iter_cloned

    // This would fail if changed.
    let _ = vec.iter().cloned().map(|x| x + "2");

    let _ = vec.iter().for_each(|x| assert!(!x.is_empty()));
    //~^ redundant_iter_cloned

    let _ = vec.iter().all(|x| x.len() == 1);
    //~^ redundant_iter_cloned

    let _ = vec.iter().any(|x| x.len() == 1);
    //~^ redundant_iter_cloned

    // Should probably stay as it is.
    let _ = [0, 1, 2, 3, 4].iter().cloned().take(10);

    // `&Range<_>` doesn't implement `IntoIterator`
    let _ = [0..1, 2..5].iter().cloned().flatten();
}

// #8527
fn cloned_flatten(x: Option<&Option<String>>) -> Option<String> {
    x.cloned().flatten()
}

mod issue_16428 {
    #[derive(Clone)]
    struct Foo;

    impl Foo {
        async fn do_async(&self) {}
    }

    fn async_move_map() -> Vec<impl std::future::Future<Output = ()>> {
        let map: std::collections::HashMap<(), Foo> = std::collections::HashMap::new();

        // Should NOT lint: async move block captures `item` by value
        map.values()
            .cloned()
            .map(|item| async move { item.do_async().await })
            .collect::<Vec<_>>()
    }

    fn async_move_for_each() {
        let map: std::collections::HashMap<(), Foo> = std::collections::HashMap::new();

        // Should NOT lint: async move block captures `item` by value
        map.values()
            .cloned()
            .for_each(|item| drop(async move { item.do_async().await }));
    }

    fn move_closure() {
        let vec = vec!["1".to_string(), "2".to_string()];

        // Should NOT lint: move closure captures `x` by value
        let _: Vec<_> = vec.iter().cloned().map(|x| move || x.len()).collect();
    }

    fn async_move_not_capturing_param() {
        let vec = vec!["1".to_string(), "2".to_string()];

        // Should lint: async move captures `y`, not `x`
        let _ = vec.iter().map(|x| {
            //~^ redundant_iter_cloned
            let y = x.len();
            async move { y }
        });
    }

    fn move_closure_not_capturing_param() {
        let vec = vec!["1".to_string(), "2".to_string()];

        // Should lint: move closure captures `y`, not `x`
        let _: Vec<_> = vec
            //~^ redundant_iter_cloned
            .iter()
            .map(|x| {
                let y = x.len();
                move || y
            })
            .collect();
    }
}
