//! Eval implementations for interval and timestamp arithmetic operators

use anyhow::{anyhow, bail};
use chrono::Duration;
use chronoutil::RelativeDuration;

use hamelin_lib::func::defs::{
    CalendarIntervalDivideInt, CalendarIntervalMinusCalendarInterval, CalendarIntervalMultiplyInt,
    CalendarIntervalPlusCalendarInterval, CalendarIntervalPlusTimestamp,
    IntMultiplyCalendarInterval, IntervalDivideNumeric, IntervalMinusInterval,
    IntervalMultiplyNumeric, IntervalPlusInterval, IntervalPlusTimestamp, NumericMultiplyInterval,
    TimestampMinusCalendarInterval, TimestampMinusInterval, TimestampMinusTimestamp,
    TimestampPlusCalendarInterval, TimestampPlusInterval,
};

use crate::registry::EvalRegistry;
use crate::reverse_eval::domain::Constraint;
use crate::value::Value;
use crate::{null_propagate, null_propagate_reverse};

/// Multiply an interval by a double factor.
fn multiply_interval(interval: &Duration, multiplier: f64) -> anyhow::Result<Duration> {
    let micros = interval
        .num_microseconds()
        .ok_or_else(|| anyhow!("Interval too large to represent in microseconds"))?;
    let result_micros = (micros as f64 * multiplier).round() as i64;
    Ok(Duration::microseconds(result_micros))
}

/// Divide an interval by a double divisor.
fn divide_interval(interval: &Duration, divisor: f64) -> anyhow::Result<Duration> {
    if divisor == 0.0 {
        bail!("Division by zero");
    }
    let micros = interval
        .num_microseconds()
        .ok_or_else(|| anyhow!("Interval too large to represent in microseconds"))?;
    let result_micros = (micros as f64 / divisor).round() as i64;
    Ok(Duration::microseconds(result_micros))
}

/// Register all interval/timestamp arithmetic eval implementations.
pub fn register(registry: &mut EvalRegistry) {
    // ========================================================================
    // Interval * Numeric
    // ========================================================================
    registry.register_eval::<IntervalMultiplyNumeric>(|mut params| {
        let interval = null_propagate!(params.take()?).require_interval()?;
        let numeric = null_propagate!(params.take()?);
        let multiplier = numeric.coerce_require_double()?;
        let result = multiply_interval(&interval, multiplier)?;
        Ok(Value::Interval(result))
    });

    registry.register_reverse::<IntervalMultiplyNumeric>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // interval has variable, numeric is constant
                // x * n = output => x = output / n
                let multiplier = null_propagate_reverse!(right_val, output_constraint)
                    .coerce_require_double()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let result = divide_interval(out_interval, multiplier)?;
                    Ok(Value::Interval(result))
                })
            }
            (Some(left_val), None) => {
                // interval is constant, numeric has variable
                // interval * x = output => x = output / interval
                let interval =
                    null_propagate_reverse!(left_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let interval_micros = interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    if interval_micros == 0 {
                        bail!("Division by zero in reverse multiplication");
                    }
                    let out_micros = out_interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    let multiplier = out_micros as f64 / interval_micros as f64;
                    Ok(Value::Double(multiplier))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Numeric * Interval
    // ========================================================================
    registry.register_eval::<NumericMultiplyInterval>(|mut params| {
        let numeric = null_propagate!(params.take()?);
        let interval = null_propagate!(params.take()?).require_interval()?;
        let multiplier = numeric.coerce_require_double()?;
        let result = multiply_interval(&interval, multiplier)?;
        Ok(Value::Interval(result))
    });

    registry.register_reverse::<NumericMultiplyInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // numeric has variable, interval is constant
                // x * interval = output => x = output / interval
                let interval =
                    null_propagate_reverse!(right_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let interval_micros = interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    if interval_micros == 0 {
                        bail!("Division by zero in reverse multiplication");
                    }
                    let out_micros = out_interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    let multiplier = out_micros as f64 / interval_micros as f64;
                    Ok(Value::Double(multiplier))
                })
            }
            (Some(left_val), None) => {
                // numeric is constant, interval has variable
                // n * x = output => x = output / n
                let multiplier =
                    null_propagate_reverse!(left_val, output_constraint).coerce_require_double()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let result = divide_interval(out_interval, multiplier)?;
                    Ok(Value::Interval(result))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Interval / Numeric
    // ========================================================================
    registry.register_eval::<IntervalDivideNumeric>(|mut params| {
        let interval = null_propagate!(params.take()?).require_interval()?;
        let numeric = null_propagate!(params.take()?);
        let divisor = numeric.coerce_require_double()?;
        let result = divide_interval(&interval, divisor)?;
        Ok(Value::Interval(result))
    });

    registry.register_reverse::<IntervalDivideNumeric>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // interval has variable, numeric is constant
                // x / n = output => x = output * n
                let divisor = null_propagate_reverse!(right_val, output_constraint)
                    .coerce_require_double()?;

                // Division by zero is undefined, so there's no valid value of x
                if divisor == 0.0 {
                    return Ok(Constraint::Empty);
                }

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let result = multiply_interval(out_interval, divisor)?;
                    Ok(Value::Interval(result))
                })
            }
            (Some(left_val), None) => {
                // interval is constant, numeric has variable
                // interval / x = output => x = interval / output
                let interval =
                    null_propagate_reverse!(left_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let interval_micros = interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    let out_micros = out_interval.num_microseconds().ok_or_else(|| {
                        anyhow!("Interval too large to represent in microseconds")
                    })?;
                    if out_micros == 0 {
                        bail!("Division by zero in reverse division");
                    }
                    Ok(Value::Double(interval_micros as f64 / out_micros as f64))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // CalendarInterval + CalendarInterval
    // ========================================================================
    registry.register_eval::<CalendarIntervalPlusCalendarInterval>(|mut params| {
        let left_ci = null_propagate!(params.take()?).require_calendar_interval()?;
        let right_ci = null_propagate!(params.take()?).require_calendar_interval()?;
        Ok(Value::CalendarInterval(left_ci + right_ci))
    });

    registry.register_reverse::<CalendarIntervalPlusCalendarInterval>(
        |output_constraint, params| {
            let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
            let right = params
                .get_by_name("right")
                .ok()
                .and_then(|opt| opt.as_ref());

            match (left, right) {
                (None, Some(right_val)) => {
                    // left calendar_interval has variable, right is constant
                    // x + ci = output => x = output - ci
                    let right_ci = null_propagate_reverse!(right_val, output_constraint)
                        .try_calendar_interval()?;

                    output_constraint.map(|output_val| {
                        let out_ci = output_val.try_calendar_interval()?;
                        Ok(Value::CalendarInterval(out_ci - right_ci))
                    })
                }
                (Some(left_val), None) => {
                    // left calendar_interval is constant, right has variable
                    // ci + x = output => x = output - ci
                    let left_ci = null_propagate_reverse!(left_val, output_constraint)
                        .try_calendar_interval()?;

                    output_constraint.map(|output_val| {
                        let out_ci = output_val.try_calendar_interval()?;
                        Ok(Value::CalendarInterval(out_ci - left_ci))
                    })
                }
                _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
            }
        },
    );

    // ========================================================================
    // CalendarInterval - CalendarInterval
    // ========================================================================
    registry.register_eval::<CalendarIntervalMinusCalendarInterval>(|mut params| {
        let left_ci = null_propagate!(params.take()?).require_calendar_interval()?;
        let right_ci = null_propagate!(params.take()?).require_calendar_interval()?;
        Ok(Value::CalendarInterval(left_ci - right_ci))
    });

    registry.register_reverse::<CalendarIntervalMinusCalendarInterval>(
        |output_constraint, params| {
            let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
            let right = params
                .get_by_name("right")
                .ok()
                .and_then(|opt| opt.as_ref());

            match (left, right) {
                (None, Some(right_val)) => {
                    // left calendar_interval has variable, right is constant
                    // x - ci = output => x = output + ci
                    let right_ci = null_propagate_reverse!(right_val, output_constraint)
                        .try_calendar_interval()?;

                    output_constraint.map(|output_val| {
                        let out_ci = output_val.try_calendar_interval()?;
                        Ok(Value::CalendarInterval(out_ci + right_ci))
                    })
                }
                (Some(left_val), None) => {
                    // left calendar_interval is constant, right has variable
                    // ci - x = output => x = ci - output
                    let left_ci = null_propagate_reverse!(left_val, output_constraint)
                        .try_calendar_interval()?;

                    output_constraint.map(|output_val| {
                        let out_ci = output_val.try_calendar_interval()?;
                        Ok(Value::CalendarInterval(left_ci - out_ci))
                    })
                }
                _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
            }
        },
    );

    // ========================================================================
    // CalendarInterval * Int
    // ========================================================================
    registry.register_eval::<CalendarIntervalMultiplyInt>(|mut params| {
        let calendar_interval = null_propagate!(params.take()?).require_calendar_interval()?;
        let numeric = null_propagate!(params.take()?);
        let multiplier = numeric.require_int()?;
        Ok(Value::CalendarInterval(
            calendar_interval * multiplier as i32,
        ))
    });

    registry.register_reverse::<CalendarIntervalMultiplyInt>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // calendar_interval has variable, numeric is constant
                // x * n = output => x = output / n
                let multiplier = null_propagate_reverse!(right_val, output_constraint).try_int()?;
                if multiplier == 0 {
                    bail!("Division by zero in reverse multiplication");
                }

                output_constraint.map(|output_val| {
                    let out_ci = output_val.try_calendar_interval()?;
                    Ok(Value::CalendarInterval(out_ci / multiplier as i32))
                })
            }
            (Some(left_val), None) => {
                // calendar_interval is constant, numeric has variable
                // ci * x = output => x = output / ci (complex, not easily invertible)
                null_propagate_reverse!(left_val, output_constraint);
                bail!("Cannot solve for numeric multiplier in calendar interval multiplication")
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Int * CalendarInterval
    // ========================================================================
    registry.register_eval::<IntMultiplyCalendarInterval>(|mut params| {
        let numeric = null_propagate!(params.take()?);
        let calendar_interval = null_propagate!(params.take()?).require_calendar_interval()?;
        let multiplier = numeric.require_int()?;
        Ok(Value::CalendarInterval(
            calendar_interval * multiplier as i32,
        ))
    });

    registry.register_reverse::<IntMultiplyCalendarInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // numeric has variable, calendar_interval is constant
                // x * ci = output => x = output / ci (complex, not easily invertible)
                null_propagate_reverse!(right_val, output_constraint);
                bail!("Cannot solve for numeric multiplier in calendar interval multiplication")
            }
            (Some(left_val), None) => {
                // numeric is constant, calendar_interval has variable
                // n * x = output => x = output / n
                let multiplier = null_propagate_reverse!(left_val, output_constraint).try_int()?;
                if multiplier == 0 {
                    bail!("Division by zero in reverse multiplication");
                }

                output_constraint.map(|output_val| {
                    let out_ci = output_val.try_calendar_interval()?;
                    Ok(Value::CalendarInterval(out_ci / multiplier as i32))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // CalendarInterval / Int
    // ========================================================================
    registry.register_eval::<CalendarIntervalDivideInt>(|mut params| {
        let calendar_interval = null_propagate!(params.take()?).require_calendar_interval()?;
        let numeric = null_propagate!(params.take()?);
        let divisor = numeric.require_int()?;
        if divisor == 0 {
            bail!("Division by zero");
        }
        Ok(Value::CalendarInterval(calendar_interval / divisor as i32))
    });

    registry.register_reverse::<CalendarIntervalDivideInt>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // calendar_interval has variable, numeric is constant
                // x / n = output => x = output * n
                let divisor = null_propagate_reverse!(right_val, output_constraint).try_int()?;

                // Division by zero is undefined, so there's no valid value of x
                if divisor == 0 {
                    return Ok(Constraint::Empty);
                }

                output_constraint.map(|output_val| {
                    let out_ci = output_val.try_calendar_interval()?;
                    Ok(Value::CalendarInterval(out_ci * divisor as i32))
                })
            }
            (Some(left_val), None) => {
                // calendar_interval is constant, numeric has variable
                // ci / x = output => x = ci / output (complex, not easily invertible)
                null_propagate_reverse!(left_val, output_constraint);
                bail!("Cannot solve for numeric divisor in calendar interval division")
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Timestamp - Interval
    // ========================================================================
    registry.register_eval::<TimestampMinusInterval>(|mut params| {
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let interval = null_propagate!(params.take()?).require_interval()?;
        Ok((timestamp - interval).into())
    });

    registry.register_reverse::<TimestampMinusInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // timestamp has variable, interval is constant
                // t - interval = output => t = output + interval
                let interval =
                    null_propagate_reverse!(right_val, output_constraint).try_interval()?;
                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let input_ts = out_ts + *interval;
                    Ok(input_ts.into())
                })
            }
            (Some(left_val), None) => {
                // timestamp is constant, interval has variable
                // ts - x = output => x = ts - output
                let ts = null_propagate_reverse!(left_val, output_constraint).try_timestamp()?;

                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let interval = ts - out_ts;
                    Ok(Value::Interval(interval))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Timestamp - CalendarInterval
    // ========================================================================
    registry.register_eval::<TimestampMinusCalendarInterval>(|mut params| {
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let months = null_propagate!(params.take()?).require_calendar_interval()?;
        let negated = RelativeDuration::months(-months);
        Ok((timestamp + negated).into())
    });

    registry.register_reverse::<TimestampMinusCalendarInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // timestamp has variable, calendar_interval is constant
                // t - ci = output => t = output + ci
                let months = null_propagate_reverse!(right_val, output_constraint)
                    .try_calendar_interval()?;
                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let input_ts = out_ts + RelativeDuration::months(months);
                    Ok(input_ts.into())
                })
            }
            (Some(left_val), None) => {
                // timestamp is constant, calendar_interval has variable
                // ts - x = output => x = ts - output
                // Cannot solve: timestamp - timestamp yields fixed Interval, not CalendarInterval
                null_propagate_reverse!(left_val, output_constraint);
                bail!("Cannot solve for calendar interval from timestamp arithmetic")
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Interval + Timestamp
    // ========================================================================
    registry.register_eval::<IntervalPlusTimestamp>(|mut params| {
        let interval = null_propagate!(params.take()?).require_interval()?;
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        Ok((timestamp + interval).into())
    });

    registry.register_reverse::<IntervalPlusTimestamp>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // interval has variable, timestamp is constant
                // x + ts = output => x = output - ts
                let ts = null_propagate_reverse!(right_val, output_constraint).try_timestamp()?;

                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let interval = out_ts - ts;
                    Ok(Value::Interval(interval))
                })
            }
            (Some(left_val), None) => {
                // interval is constant, timestamp has variable
                // interval + t = output => t = output - interval
                let interval =
                    null_propagate_reverse!(left_val, output_constraint).try_interval()?;
                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let input_ts = out_ts - *interval;
                    Ok(input_ts.into())
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // CalendarInterval + Timestamp
    // ========================================================================
    registry.register_eval::<CalendarIntervalPlusTimestamp>(|mut params| {
        let months = null_propagate!(params.take()?).require_calendar_interval()?;
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        Ok((timestamp + RelativeDuration::months(months)).into())
    });

    registry.register_reverse::<CalendarIntervalPlusTimestamp>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // calendar_interval has variable, timestamp is constant
                // x + ts = output => x = output - ts
                // Cannot solve: timestamp - timestamp yields fixed Interval, not CalendarInterval
                null_propagate_reverse!(right_val, output_constraint);
                bail!("Cannot solve for calendar interval from timestamp arithmetic")
            }
            (Some(left_val), None) => {
                // calendar_interval is constant, timestamp has variable
                // ci + t = output => t = output - ci
                let months =
                    null_propagate_reverse!(left_val, output_constraint).try_calendar_interval()?;
                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let negated = RelativeDuration::months(-months);
                    let input_ts = out_ts + negated;
                    Ok(input_ts.into())
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Timestamp - Timestamp
    // ========================================================================
    registry.register_eval::<TimestampMinusTimestamp>(|mut params| {
        let left_timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let right_timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let diff = left_timestamp - right_timestamp;
        Ok(Value::Interval(diff))
    });

    registry.register_reverse::<TimestampMinusTimestamp>(|output_constraint, params| {
        // Check which parameter is constant
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // left timestamp has variable, right timestamp is constant
                // t1 - t2 = interval => t1 = t2 + interval
                let right_ts =
                    null_propagate_reverse!(right_val, output_constraint).try_timestamp()?;

                output_constraint.map(|output_val| {
                    let interval = output_val.try_interval()?;
                    let left_ts = right_ts + *interval;
                    Ok(left_ts.into())
                })
            }
            (Some(left_val), None) => {
                // left timestamp is constant, right timestamp has variable
                // t1 - t2 = interval => t2 = t1 - interval
                let left_ts =
                    null_propagate_reverse!(left_val, output_constraint).try_timestamp()?;

                output_constraint.map(|output_val| {
                    let interval = output_val.try_interval()?;
                    let right_ts = left_ts - *interval;
                    Ok(right_ts.into())
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Timestamp + Interval
    // ========================================================================
    registry.register_eval::<TimestampPlusInterval>(|mut params| {
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let interval = null_propagate!(params.take()?).require_interval()?;
        Ok((timestamp + interval).into())
    });

    registry.register_reverse::<TimestampPlusInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // timestamp has variable, interval is constant
                // t + interval = output => t = output - interval
                let interval =
                    null_propagate_reverse!(right_val, output_constraint).try_interval()?;
                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let input_ts = out_ts - *interval;
                    Ok(input_ts.into())
                })
            }
            (Some(left_val), None) => {
                // timestamp is constant, interval has variable
                // ts + x = output => x = output - ts
                let ts = null_propagate_reverse!(left_val, output_constraint).try_timestamp()?;

                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    let interval = out_ts - ts;
                    Ok(Value::Interval(interval))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Timestamp + CalendarInterval
    // ========================================================================
    registry.register_eval::<TimestampPlusCalendarInterval>(|mut params| {
        let timestamp = null_propagate!(params.take()?).require_timestamp()?;
        let months = null_propagate!(params.take()?).require_calendar_interval()?;
        let result = timestamp + RelativeDuration::months(months);
        Ok(result.into())
    });

    registry.register_reverse::<TimestampPlusCalendarInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // timestamp has variable, calendar_interval is constant
                // t + ci = output => t = output - ci
                let months = null_propagate_reverse!(right_val, output_constraint)
                    .try_calendar_interval()?;

                output_constraint.map(|output_val| {
                    let out_ts = output_val.try_timestamp()?;
                    // Subtract calendar interval by negating and applying
                    let negated = RelativeDuration::months(-months);
                    let input_ts = out_ts + negated;
                    Ok(input_ts.into())
                })
            }
            (Some(left_val), None) => {
                // timestamp is constant, calendar_interval has variable
                // ts + x = output => x = output - ts
                // Cannot solve: timestamp - timestamp yields fixed Interval, not CalendarInterval
                null_propagate_reverse!(left_val, output_constraint);
                bail!("Cannot solve for calendar interval from timestamp arithmetic")
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Interval + Interval
    // ========================================================================
    registry.register_eval::<IntervalPlusInterval>(|mut params| {
        let left_interval = null_propagate!(params.take()?).require_interval()?;
        let right_interval = null_propagate!(params.take()?).require_interval()?;
        Ok(Value::Interval(left_interval + right_interval))
    });

    registry.register_reverse::<IntervalPlusInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // left interval has variable, right interval is constant
                // x + interval = output => x = output - interval
                let right_interval =
                    null_propagate_reverse!(right_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let left_interval = *out_interval - *right_interval;
                    Ok(Value::Interval(left_interval))
                })
            }
            (Some(left_val), None) => {
                // left interval is constant, right interval has variable
                // interval + x = output => x = output - interval
                let left_interval =
                    null_propagate_reverse!(left_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let right_interval = *out_interval - *left_interval;
                    Ok(Value::Interval(right_interval))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });

    // ========================================================================
    // Interval - Interval
    // ========================================================================
    registry.register_eval::<IntervalMinusInterval>(|mut params| {
        let left_interval = null_propagate!(params.take()?).require_interval()?;
        let right_interval = null_propagate!(params.take()?).require_interval()?;
        Ok(Value::Interval(left_interval - right_interval))
    });

    registry.register_reverse::<IntervalMinusInterval>(|output_constraint, params| {
        let left = params.get_by_name("left").ok().and_then(|opt| opt.as_ref());
        let right = params
            .get_by_name("right")
            .ok()
            .and_then(|opt| opt.as_ref());

        match (left, right) {
            (None, Some(right_val)) => {
                // left interval has variable, right interval is constant
                // x - interval = output => x = output + interval
                let right_interval =
                    null_propagate_reverse!(right_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let left_interval = *out_interval + *right_interval;
                    Ok(Value::Interval(left_interval))
                })
            }
            (Some(left_val), None) => {
                // left interval is constant, right interval has variable
                // interval - x = output => x = interval - output
                let left_interval =
                    null_propagate_reverse!(left_val, output_constraint).try_interval()?;

                output_constraint.map(|output_val| {
                    let out_interval = output_val.try_interval()?;
                    let right_interval = *left_interval - *out_interval;
                    Ok(Value::Interval(right_interval))
                })
            }
            _ => bail!("Invalid reverse evaluation: expected exactly one variable"),
        }
    });
}
