use crate::prelude::*;
use crate::series::arithmetic::coerce_lhs_rhs;

fn min_binary<T>(left: &ChunkedArray<T>, right: &ChunkedArray<T>) -> ChunkedArray<T>
where
    T: PolarsNumericType,
    T::Native: PartialOrd,
{
    let op = |l: T::Native, r: T::Native| {
        if l < r {
            l
        } else {
            r
        }
    };
    arity::binary_elementwise_values(left, right, op)
}

fn max_binary<T>(left: &ChunkedArray<T>, right: &ChunkedArray<T>) -> ChunkedArray<T>
where
    T: PolarsNumericType,
    T::Native: PartialOrd,
{
    let op = |l: T::Native, r: T::Native| {
        if l > r {
            l
        } else {
            r
        }
    };
    arity::binary_elementwise_values(left, right, op)
}

pub(crate) fn min_max_binary_columns(
    left: &Column,
    right: &Column,
    min: bool,
) -> PolarsResult<Column> {
    if left.dtype().to_physical().is_numeric()
        && left.null_count() == 0
        && right.null_count() == 0
        && left.len() == right.len()
    {
        match (left, right) {
            (Column::Series(left), Column::Series(right)) => {
                let (lhs, rhs) = coerce_lhs_rhs(left, right)?;
                let logical = lhs.dtype();
                let lhs = lhs.to_physical_repr();
                let rhs = rhs.to_physical_repr();

                with_match_physical_numeric_polars_type!(lhs.dtype(), |$T| {
                    let a: &ChunkedArray<$T> = lhs.as_ref().as_ref().as_ref();
                    let b: &ChunkedArray<$T> = rhs.as_ref().as_ref().as_ref();

                    if min {
                        min_binary(a, b).into_series().cast(logical)
                    } else {
                        max_binary(a, b).into_series().cast(logical)
                    }
                })
                .map(Column::from)
            },
            _ => {
                let mask = if min {
                    left.lt(right)?
                } else {
                    left.gt(right)?
                };

                left.zip_with(&mask, right)
            },
        }
    } else {
        let mask = if min {
            left.lt(right)? & left.is_not_null() | right.is_null()
        } else {
            left.gt(right)? & left.is_not_null() | right.is_null()
        };
        left.zip_with(&mask, right)
    }
}
