///|
fn eval_date(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] =>
      match (value_as_int(v0), value_as_int(v1), value_as_int(v2)) {
        (Ok(year), Ok(month), Ok(day)) =>
          match
            excel_serial_from_date(
              year,
              month,
              day,
              use_1904_dates=call.ctx.use_1904_dates,
            ) {
            Some(serial) => Number(serial)
            None => Error(formula_error_num)
          }
        (Err(err), _, _) => err
        (_, Err(err), _) => err
        (_, _, Err(err)) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_time(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] =>
      match (value_as_int(v0), value_as_int(v1), value_as_int(v2)) {
        (Ok(hours), Ok(minutes), Ok(seconds)) =>
          match excel_time_fraction(hours, minutes, seconds) {
            Some(value) => Number(value)
            None => Error(formula_error_num)
          }
        (Err(err), _, _) => err
        (_, Err(err), _) => err
        (_, _, Err(err)) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_datevalue(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => {
      let text = match value_as_string(v0) {
        Ok(value) => value
        Err(err) => return err
      }
      match parse_date_parts(text) {
        Some((year, month, day)) =>
          match
            excel_serial_from_date(
              year,
              month,
              day,
              use_1904_dates=call.ctx.use_1904_dates,
            ) {
            Some(serial) => Number(serial)
            None => Error(formula_error_value)
          }
        None => Error(formula_error_value)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_timevalue(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => {
      let text = match value_as_string(v0) {
        Ok(value) => value
        Err(err) => return err
      }
      match parse_time_parts(text) {
        Some((hours, minutes, seconds)) =>
          // parse_time_parts only yields non-negative components.
          Number(excel_time_fraction(hours, minutes, seconds).unwrap())
        None => Error(formula_error_value)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_datedif(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1, v2] => {
      let start_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let end_serial = match
        value_as_date_serial(v1, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      if start_serial > end_serial {
        Error(formula_error_num)
      } else if start_serial == end_serial {
        Number(0.0)
      } else {
        let unit = match value_as_string(v2) {
          Ok(text) => text.to_lower()
          Err(err) => return err
        }
        let (sy, sm, sd) = match
          date_parts_from_serial(
            start_serial,
            use_1904_dates=call.ctx.use_1904_dates,
          ) {
          Some(parts) => parts
          None => return Error(formula_error_num)
        }
        let (ey, em, ed) = match
          date_parts_from_serial(
            end_serial,
            use_1904_dates=call.ctx.use_1904_dates,
          ) {
          Some(parts) => parts
          None => return Error(formula_error_num)
        }
        match unit {
          "y" => {
            let mut diff = ey - sy
            if em < sm || (em == sm && ed < sd) {
              diff = diff - 1
            }
            Number(Double::from_int(diff))
          }
          "m" => {
            let mut diff = (ey - sy) * 12 + (em - sm)
            if ed < sd {
              diff = diff - 1
            }
            Number(Double::from_int(diff))
          }
          "d" => Number(end_serial - start_serial)
          "ym" => {
            let mut diff = em - sm
            if ed < sd {
              diff = diff - 1
            }
            if diff < 0 {
              diff = diff + 12
            }
            Number(Double::from_int(diff))
          }
          "yd" => {
            let mut target_year = sy
            if em < sm || (em == sm && ed < sd) {
              target_year = sy + 1
            }
            let start_base = excel_serial_from_date(
              sy,
              sm,
              sd,
              use_1904_dates=call.ctx.use_1904_dates,
            ).unwrap()
            let end_base = excel_serial_from_date(
              target_year,
              em,
              ed,
              use_1904_dates=call.ctx.use_1904_dates,
            ).unwrap()
            Number(end_base - start_base)
          }
          "md" => {
            let diff = if ed >= sd {
              ed - sd
            } else {
              let (prev_year, prev_month) = normalize_year_month(ey, em - 1)
              let dim = days_in_month(prev_year, prev_month)
              dim - sd + ed
            }
            Number(Double::from_int(diff))
          }
          _ => Error(formula_error_value)
        }
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_days(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let end_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let start_serial = match
        value_as_date_serial(v1, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      Number(end_serial - start_serial)
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_days360(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let start_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let end_serial = match
        value_as_date_serial(v1, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let (sy, sm, sd) = match
        date_parts_from_serial(
          start_serial,
          use_1904_dates=call.ctx.use_1904_dates,
        ) {
        Some(parts) => parts
        None => return Error(formula_error_num)
      }
      let (ey, em, ed) = match
        date_parts_from_serial(
          end_serial,
          use_1904_dates=call.ctx.use_1904_dates,
        ) {
        Some(parts) => parts
        None => return Error(formula_error_num)
      }
      let mut start_day = sd
      let mut end_day = ed
      let end_year = ey
      let mut end_month = em
      let use_european = if call.values is [_, _, v2] {
        match value_as_bool(v2) {
          Ok(flag) => flag
          Err(err) => return err
        }
      } else {
        false
      }
      if use_european {
        if start_day == 31 {
          start_day = 30
        }
        if end_day == 31 {
          end_day = 30
        }
      } else {
        if days_in_month(sy, sm) == start_day {
          start_day = 30
        }
        if end_day > 30 {
          if start_day < 30 {
            end_month = end_month + 1
            end_day = 1
          } else {
            end_day = 30
          }
        }
      }
      let (norm_year, norm_month) = normalize_year_month(end_year, end_month)
      let diff = (norm_year - sy) * 360 +
        (norm_month - sm) * 30 +
        (end_day - start_day)
      Number(Double::from_int(diff))
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_isoweeknum(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] => {
      let serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      // Serial zero is the 1900 system's artificial day before its epoch,
      // but is the real 1904-01-01 epoch in a date-1904 workbook.
      if !call.ctx.use_1904_dates && serial < 1.0 {
        return Error(formula_error_num)
      }
      // Excel assigns the fictitious 1900-02-29 (including its time
      // fraction) to ISO week 9. It cannot pass through the Gregorian helper
      // below because 1900 was not actually a leap year.
      if is_1900_phantom_date_serial(serial, call.ctx.use_1904_dates) {
        return Number(9.0)
      }
      let (year, month, day) = match
        date_parts_from_serial(serial, use_1904_dates=call.ctx.use_1904_dates) {
        Some(parts) => parts
        None => return Error(formula_error_num)
      }
      match iso_week_number(year, month, day) {
        Some(week) => Number(Double::from_int(week))
        None => Error(formula_error_num)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_edate(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let (year, month, day) = match
        date_parts_from_value(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(parts) => parts
        Err(err) => return err
      }
      let months = match value_as_number(v1) {
        Ok(num) => Double::to_int(trunc_double(num))
        Err(err) => return err
      }
      let (new_year, new_month) = normalize_year_month(year, month + months)
      let max_day = days_in_month(new_year, new_month)
      let new_day = if day > max_day { max_day } else { day }
      match
        excel_serial_from_date(
          new_year,
          new_month,
          new_day,
          use_1904_dates=call.ctx.use_1904_dates,
        ) {
        Some(serial) => Number(serial)
        None => Error(formula_error_num)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_eomonth(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] => {
      let (year, month, _) = match
        date_parts_from_value(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(parts) => parts
        Err(err) => return err
      }
      let months = match value_as_number(v1) {
        Ok(num) => Double::to_int(trunc_double(num))
        Err(err) => return err
      }
      let (new_year, new_month) = normalize_year_month(year, month + months)
      let new_day = days_in_month(new_year, new_month)
      match
        excel_serial_from_date(
          new_year,
          new_month,
          new_day,
          use_1904_dates=call.ctx.use_1904_dates,
        ) {
        Some(serial) => Number(serial)
        None => Error(formula_error_num)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_yearfrac(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let start_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let end_serial = match
        value_as_date_serial(v1, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let basis = if call.values is [_, _, v2] {
        match value_as_number(v2) {
          Ok(num) => Double::to_int(trunc_double(num))
          Err(err) => return err
        }
      } else {
        0
      }
      yearfrac_value(
        start_serial,
        end_serial,
        basis,
        use_1904_dates=call.ctx.use_1904_dates,
      )
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_weekday(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let (year, month, day) = match
        date_parts_from_value(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(parts) => parts
        Err(err) => return err
      }
      let mut return_type = 1
      if call.values is [_, v1] {
        return_type = match value_as_number(v1) {
          Ok(num) => Double::to_int(trunc_double(num))
          Err(err) => return err
        }
      }
      if return_type == 2 {
        return_type = 11
      }
      let weekday = weekday_sun1(year, month, day)
      if return_type == 1 {
        Number(Double::from_int(weekday))
      } else if return_type == 3 {
        let value = (weekday + 6 - 1) % 7
        Number(Double::from_int(value))
      } else if return_type >= 11 && return_type <= 17 {
        let value = (weekday + 6 - (return_type - 10)) % 7 + 1
        Number(Double::from_int(value))
      } else {
        Error(formula_error_value)
      }
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_weeknum(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] | [v0, _] => {
      let (year, month, day) = match
        date_parts_from_value(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(parts) => parts
        Err(err) => return err
      }
      let mut return_type = 1
      if call.values is [_, v1] {
        return_type = match value_as_number(v1) {
          Ok(num) => Double::to_int(trunc_double(num))
          Err(err) => return err
        }
      }
      let days = day_of_year(year, month, day)
      let mut week_mod = days % 7
      if week_mod == 0 {
        week_mod = 7
      }
      let mut week_num = (days + 6) / 7
      let first_weekday = weekday_sun1(year, 1, 1) - 1
      let offset = match return_type {
        1 | 17 => 0
        2 | 11 | 21 => 1
        12 | 13 | 14 | 15 | 16 => return_type - 10
        _ => return Error(formula_error_num)
      }
      let mut padding = offset + 7 - first_weekday
      if padding > 7 {
        padding = padding - 7
      }
      if week_mod > padding {
        week_num = week_num + 1
      }
      if return_type == 21 && (first_weekday == 0 || first_weekday > 4) {
        week_num = week_num - 1
        if week_num < 1 {
          week_num = 52
          let prev_first_weekday = weekday_sun1(year - 1, 1, 1) - 1
          if prev_first_weekday < 4 {
            week_num = week_num + 1
          }
        }
      }
      Number(Double::from_int(week_num))
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_networkdays(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let start_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let end_serial = match
        value_as_date_serial(v1, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let holidays = if call.values is [_, _, v2] {
        collect_holidays(v2, use_1904_dates=call.ctx.use_1904_dates)
      } else {
        []
      }
      networkdays_intl_value(
        start_serial,
        end_serial,
        Number(1.0),
        holidays,
        use_1904_dates=call.ctx.use_1904_dates,
      )
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_networkdays_intl(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 2 && call.values.length() <= 4 {
    let start_serial = match
      value_as_date_serial(
        call.values[0],
        use_1904_dates=call.ctx.use_1904_dates,
      ) {
      Ok(value) => value
      Err(err) => return err
    }
    let end_serial = match
      value_as_date_serial(
        call.values[1],
        use_1904_dates=call.ctx.use_1904_dates,
      ) {
      Ok(value) => value
      Err(err) => return err
    }
    let weekend_value = if call.values.length() >= 3 {
      call.values[2]
    } else {
      Number(1.0)
    }
    let holidays = if call.values.length() == 4 {
      collect_holidays(call.values[3], use_1904_dates=call.ctx.use_1904_dates)
    } else {
      []
    }
    networkdays_intl_value(
      start_serial,
      end_serial,
      weekend_value,
      holidays,
      use_1904_dates=call.ctx.use_1904_dates,
    )
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_workday(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0, v1] | [v0, v1, _] => {
      let start_serial = match
        value_as_date_serial(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok(value) => value
        Err(err) => return err
      }
      let days = match value_as_number(v1) {
        Ok(num) => num
        Err(err) => return err
      }
      let holidays = if call.values is [_, _, v2] {
        collect_holidays(v2, use_1904_dates=call.ctx.use_1904_dates)
      } else {
        []
      }
      workday_intl_value(
        start_serial,
        days,
        Number(1.0),
        holidays,
        use_1904_dates=call.ctx.use_1904_dates,
      )
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_workday_intl(call : EvalCall) -> FormulaValue {
  if call.values.length() >= 2 && call.values.length() <= 4 {
    let start_serial = match
      value_as_date_serial(
        call.values[0],
        use_1904_dates=call.ctx.use_1904_dates,
      ) {
      Ok(value) => value
      Err(err) => return err
    }
    let days = match value_as_number(call.values[1]) {
      Ok(num) => num
      Err(err) => return err
    }
    let weekend_value = if call.values.length() >= 3 {
      call.values[2]
    } else {
      Number(1.0)
    }
    let holidays = if call.values.length() == 4 {
      collect_holidays(call.values[3], use_1904_dates=call.ctx.use_1904_dates)
    } else {
      []
    }
    workday_intl_value(
      start_serial,
      days,
      weekend_value,
      holidays,
      use_1904_dates=call.ctx.use_1904_dates,
    )
  } else {
    Error(formula_error_value)
  }
}

///|
fn eval_now(call : EvalCall) -> FormulaValue {
  match call.values {
    [] => {
      let now_ms = @env.now()
      let total_seconds = UInt64::to_double(now_ms) / 1000.0
      let days = Double::floor(total_seconds / 86400.0)
      let seconds_in_day = total_seconds - days * 86400.0
      let epoch_serial = if call.ctx.use_1904_dates { 24107.0 } else { 25569.0 }
      let serial = days + epoch_serial + seconds_in_day / 86400.0
      Number(serial)
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_today(call : EvalCall) -> FormulaValue {
  match call.values {
    [] => {
      let now_ms = @env.now()
      let total_seconds = UInt64::to_double(now_ms) / 1000.0
      let days = Double::floor(total_seconds / 86400.0)
      let epoch_serial = if call.ctx.use_1904_dates { 24107.0 } else { 25569.0 }
      Number(days + epoch_serial)
    }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_year(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match
        excel_serial_date_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((year, _, _)) => Number(Double::from_int(year))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_month(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match
        excel_serial_date_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((_, month, _)) => Number(Double::from_int(month))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_day(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match
        excel_serial_date_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((_, _, day)) => Number(Double::from_int(day))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_hour(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match excel_serial_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((_, _, _, hour, _, _)) => Number(Double::from_int(hour))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_minute(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match excel_serial_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((_, _, _, _, minute, _)) => Number(Double::from_int(minute))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}

///|
fn eval_second(call : EvalCall) -> FormulaValue {
  match call.values {
    [v0] =>
      match excel_serial_parts(v0, use_1904_dates=call.ctx.use_1904_dates) {
        Ok((_, _, _, _, _, second)) => Number(Double::from_int(second))
        Err(err) => err
      }
    _ => Error(formula_error_value)
  }
}