//! Arithmetic binary operators: NumericPlus, NumericMinus, NumericMultiply, NumericDivide, NumericModulo

use std::any::TypeId;
use std::cmp::{max, min};

use crate::func::def::{FunctionDef, ParameterBindingProvider, Parameters};
use crate::func::utils::{doubles_poison_and, pls_min_dec, pls_min_dec_merge};
use crate::operator::Operator;
use crate::types::decimal_type::Decimal;
use crate::types::matcher::NumericMatcher;
use crate::types::Type;

/// Numeric addition operator: `left + right` for numeric types
#[derive(Default, Clone)]
pub struct NumericPlus;

impl FunctionDef for NumericPlus {
    fn name(&self) -> &'static str {
        Operator::Plus.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", NumericMatcher::default())
            .with("right", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        doubles_poison_and(pls_min_dec, pls_min_dec_merge)(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Numeric subtraction operator: `left - right` for numeric types
#[derive(Default, Clone)]
pub struct NumericMinus;

impl FunctionDef for NumericMinus {
    fn name(&self) -> &'static str {
        Operator::Minus.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", NumericMatcher::default())
            .with("right", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        doubles_poison_and(pls_min_dec, pls_min_dec_merge)(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Numeric multiplication operator: `left * right` for numeric types
#[derive(Default, Clone)]
pub struct NumericMultiply;

impl FunctionDef for NumericMultiply {
    fn name(&self) -> &'static str {
        Operator::Asterisk.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", NumericMatcher::default())
            .with("right", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        // trino: typeof(CAST(30.5 AS DECIMAL(3, 1)) * CAST(2.0 as INTEGER)) -> decimal(13, 1)
        doubles_poison_and(
            |dec| Decimal::new(min(dec.precision + 10, 38), dec.scale),
            |left, right| {
                Decimal::new(
                    min(38, left.precision + right.precision),
                    left.scale + right.scale,
                )
            },
        )(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Numeric division operator: `left / right` for numeric types
#[derive(Default, Clone)]
pub struct NumericDivide;

impl FunctionDef for NumericDivide {
    fn name(&self) -> &'static str {
        Operator::Slash.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", NumericMatcher::default())
            .with("right", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        doubles_poison_and(
            |dec| Ok(dec.clone()),
            |left, right| {
                Decimal::new(
                    min(
                        38,
                        left.precision + right.scale - left.scale
                            + max(0, right.scale - left.scale),
                    ),
                    max(left.scale, right.precision),
                )
            },
        )(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// Numeric modulo operator: `left % right` for numeric types
#[derive(Default, Clone)]
pub struct NumericModulo;

impl FunctionDef for NumericModulo {
    fn name(&self) -> &'static str {
        Operator::Percent.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", NumericMatcher::default())
            .with("right", NumericMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        doubles_poison_and(
            |dec| Ok(dec.clone()),
            |left, right| {
                Decimal::new(
                    min(left.precision - left.scale, right.precision - right.scale)
                        + max(left.scale, right.scale),
                    max(left.scale, right.scale),
                )
            },
        )(bindings)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}
