//! Array and collection utilities.
//!
//! This module provides pure functional helpers for working with slices and vectors,
//! inspired by Laravel's Collection API.
//!
//! # Example
//!
//! ```rust
//! use rok_utils::arr::{map, filter, find};
//!
//! let numbers = [1, 2, 3, 4, 5];
//!
//! let doubled = map(&numbers, |x| x * 2);
//! assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
//!
//! let evens = filter(&numbers, |x| x % 2 == 0);
//! assert_eq!(evens, vec![2, 4]);
//! ```

use std::collections::HashMap;

pub fn map<T, U>(arr: &[T], f: impl Fn(&T) -> U) -> Vec<U> {
    arr.iter().map(f).collect()
}

pub fn filter<T: Clone>(arr: &[T], f: impl Fn(&T) -> bool) -> Vec<T> {
    arr.iter().filter(|x| f(x)).cloned().collect()
}

pub fn filter_map<T, U>(arr: &[T], f: impl Fn(&T) -> Option<U>) -> Vec<U> {
    arr.iter().filter_map(f).collect()
}

pub fn reduce<T, A>(arr: &[T], init: A, f: impl Fn(A, &T) -> A) -> A {
    arr.iter().fold(init, f)
}

pub fn chunk<T: Clone>(arr: &[T], size: usize) -> Vec<Vec<T>> {
    if size == 0 {
        return vec![arr.to_vec()];
    }
    arr.chunks(size).map(|c| c.to_vec()).collect()
}

pub fn flatten<T: Clone>(arr: &[Vec<T>]) -> Vec<T> {
    arr.iter().flatten().cloned().collect()
}

pub fn compact<T: Clone + Default + PartialEq>(arr: &[T]) -> Vec<T> {
    arr.iter()
        .filter(|x| **x != T::default())
        .cloned()
        .collect()
}

pub fn take<T: Clone>(arr: &[T], n: usize) -> Vec<T> {
    arr.iter().take(n).cloned().collect()
}

pub fn skip<T: Clone>(arr: &[T], n: usize) -> Vec<T> {
    arr.iter().skip(n).cloned().collect()
}

pub fn reverse<T: Clone>(arr: &[T]) -> Vec<T> {
    let mut out: Vec<T> = arr.to_vec();
    out.reverse();
    out
}

pub fn zip<A: Clone, B: Clone>(a: &[A], b: &[B]) -> Vec<(A, B)> {
    a.iter()
        .zip(b.iter())
        .map(|(x, y)| (x.clone(), y.clone()))
        .collect()
}

pub fn first<T>(arr: &[T]) -> Option<&T> {
    arr.first()
}

pub fn last<T>(arr: &[T]) -> Option<&T> {
    arr.last()
}

pub fn get<T>(arr: &[T], index: usize) -> Option<&T> {
    arr.get(index)
}

pub fn find<T>(arr: &[T], f: impl Fn(&T) -> bool) -> Option<&T> {
    arr.iter().find(|x| f(x))
}

pub fn some<T>(arr: &[T], f: impl Fn(&T) -> bool) -> bool {
    arr.iter().any(f)
}

pub fn every<T>(arr: &[T], f: impl Fn(&T) -> bool) -> bool {
    arr.iter().all(f)
}

pub fn contains<T: PartialEq>(arr: &[T], value: &T) -> bool {
    arr.iter().any(|x| x == value)
}

pub fn group_by<T, K>(arr: &[T], key_fn: impl Fn(&T) -> K) -> HashMap<K, Vec<T>>
where
    K: Eq + std::hash::Hash,
    T: Clone,
{
    let mut map: HashMap<K, Vec<T>> = HashMap::new();
    for item in arr {
        map.entry(key_fn(item)).or_default().push(item.clone());
    }
    map
}

pub fn key_by<T, K>(arr: &[T], key_fn: impl Fn(&T) -> K) -> HashMap<K, T>
where
    K: Eq + std::hash::Hash,
    T: Clone,
{
    let mut map: HashMap<K, T> = HashMap::new();
    for item in arr {
        map.insert(key_fn(item), item.clone());
    }
    map
}

pub fn pluck<T, U>(arr: &[T], extractor: impl Fn(&T) -> U) -> Vec<U> {
    arr.iter().map(extractor).collect()
}

pub fn where_in<T: PartialEq + Clone>(arr: &[T], values: &[T]) -> Vec<T> {
    arr.iter().filter(|x| values.contains(x)).cloned().collect()
}

pub fn count<T>(arr: &[T]) -> usize {
    arr.len()
}

pub fn is_empty<T>(arr: &[T]) -> bool {
    arr.is_empty()
}

pub fn is_not_empty<T>(arr: &[T]) -> bool {
    !arr.is_empty()
}

pub fn unique<T: PartialEq + Clone>(arr: &[T]) -> Vec<T> {
    let mut seen: Vec<T> = vec![];
    arr.iter()
        .filter(|x| {
            if seen.contains(x) {
                false
            } else {
                seen.push((*x).clone());
                true
            }
        })
        .cloned()
        .collect()
}

pub fn without<T: PartialEq + Clone>(arr: &[T], values: &[T]) -> Vec<T> {
    arr.iter()
        .filter(|x| !values.contains(x))
        .cloned()
        .collect()
}

pub fn merge<T: Clone>(a: &[T], b: &[T]) -> Vec<T> {
    let mut out = a.to_vec();
    out.extend(b.iter().cloned());
    out
}

pub fn intersect<T: PartialEq + Clone>(a: &[T], b: &[T]) -> Vec<T> {
    a.iter().filter(|x| b.contains(x)).cloned().collect()
}

pub fn diff<T: PartialEq + Clone>(a: &[T], b: &[T]) -> Vec<T> {
    a.iter().filter(|x| !b.contains(x)).cloned().collect()
}

pub fn sort_by<T: Clone>(arr: &[T], cmp: impl Fn(&T, &T) -> std::cmp::Ordering) -> Vec<T> {
    let mut out = arr.to_vec();
    out.sort_by(cmp);
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_map() {
        let arr = [1, 2, 3];
        let result = map(&arr, |x| x * 2);
        assert_eq!(result, vec![2, 4, 6]);
    }

    #[test]
    fn test_filter() {
        let arr = [1, 2, 3, 4];
        let result = filter(&arr, |x| *x % 2 == 0);
        assert_eq!(result, vec![2, 4]);
    }

    #[test]
    fn test_chunk() {
        let arr = [1, 2, 3, 4, 5];
        let result = chunk(&arr, 2);
        assert_eq!(result, vec![vec![1, 2], vec![3, 4], vec![5]]);
    }

    #[test]
    fn test_unique() {
        let arr = [1, 2, 2, 3, 3, 3, 4];
        let result = unique(&arr);
        assert_eq!(result, vec![1, 2, 3, 4]);
    }

    #[test]
    fn test_contains() {
        let arr = [1, 2, 3];
        assert!(contains(&arr, &2));
        assert!(!contains(&arr, &5));
    }
}
