#![expect(
    clippy::large_stack_frames,
    reason = "iterating over large array; does not cause stack overflow"
)]

use std::hint::black_box as bb;

use criterion::Bencher;
use time::ext::{NumericalDuration, NumericalStdDuration};
use time::macros::date;
use time::{Date, Time};

/// Generate a representative sample of all dates.
///
/// The ratio of month sizes, week sizes, year sign, leap years, etc. are all identical to the full
/// range. This ensures that benchmarks accurately reflect random data. To avoid caching effects,
/// the dates are shuffled randomly but deterministically.
//
// Note that this is a _very_ large array (over 1 MiB), so we silence the warning about large stack
// frames at the top of this file.
fn representative_dates() -> [Date; 292_194] {
    static DATES: std::sync::LazyLock<[Date; 292_194]> = std::sync::LazyLock::new(|| {
        use rand09::prelude::*;

        let mut dates = [Date::MIN; _];
        let mut current = date!(-0400-01-01);
        let mut i = 0;
        while current < date!(0400-01-01) {
            dates[i] = current;
            current = current.next_day().expect("date is in range");
            i += 1;
        }

        let mut seed = SmallRng::seed_from_u64(0);
        dates.shuffle(&mut seed);

        dates
    });

    *DATES
}

setup_benchmark! {
    "Date",

    fn noop(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(date);
            }
        });
    }

    fn noop_windows(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates().windows(2) {
                let first = date[0];
                let second = date[1];
                let _ = bb((bb(first), bb(second)));
            }
        });
    }

    fn from_calendar_date(ben: &mut Bencher<'_>) {
        let dates = representative_dates().map(Date::to_calendar_date);
        ben.iter(|| {
            for (year, month, day) in dates {
                let _ = bb(Date::from_calendar_date(bb(year), bb(month), bb(day)));
            }
        });
    }

    fn from_ordinal_date(ben: &mut Bencher<'_>) {
        let dates = representative_dates().map(Date::to_ordinal_date);
        ben.iter(|| {
            for (year, ordinal) in dates {
                let _ = bb(Date::from_ordinal_date(bb(year), bb(ordinal)));
            }
        });

    }

    fn from_iso_week_date(ben: &mut Bencher<'_>) {
        let dates = representative_dates().map(Date::to_iso_week_date);
        ben.iter(|| {
            for (year, week, weekday) in dates {
                let _ = bb(Date::from_iso_week_date(bb(year), bb(week), bb(weekday)));
            }
        });
    }

    fn from_julian_day(ben: &mut Bencher<'_>) {
        let dates = representative_dates().map(Date::to_julian_day);
        ben.iter(|| {
            for julian_day in dates {
                let _ = bb(Date::from_julian_day(bb(julian_day)));
            }
        });
    }

    fn year(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).year());
            }
        });
    }

    fn month(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).month());
            }
        });
    }

    fn day(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).day());
            }
        });
    }

    fn ordinal(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).ordinal());
            }
        });
    }

    fn iso_week(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).iso_week());
            }
        });
    }

    fn sunday_based_week(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).sunday_based_week());
            }
        });
    }

    fn monday_based_week(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).monday_based_week());
            }
        });
    }

    fn to_calendar_date(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).to_calendar_date());
            }
        });
    }

    fn to_ordinal_date(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).to_ordinal_date());
            }
        });
    }

    fn to_iso_week_date(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).to_iso_week_date());
            }
        });
    }

    fn weekday(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).weekday());
            }
        });
    }

    fn next_day(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).next_day());
            }
        });
    }

    fn previous_day(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).previous_day());
            }
        });
    }

    fn to_julian_day(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).to_julian_day());
            }
        });
    }

    fn midnight(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).midnight());
            }
        });
    }

    fn with_time(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).with_time(bb(Time::MIDNIGHT)));
            }
        });
    }

    fn with_hms(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).with_hms(bb(0), bb(0), bb(0)));
            }
        });
    }

    fn with_hms_milli(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).with_hms_milli(bb(0), bb(0), bb(0), bb(0)));
            }
        });
    }

    fn with_hms_micro(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).with_hms_micro(bb(0), bb(0), bb(0), bb(0)));
            }
        });
    }

    fn with_hms_nano(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date).with_hms_nano(bb(0), bb(0), bb(0), bb(0)));
            }
        });
    }

    fn add(ben: &mut Bencher<'_>) {
        let dt = 5.days();
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date) + bb(dt));
            }
        });
    }

    fn add_std(ben: &mut Bencher<'_>) {
        let dt = 5.std_days();
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date) - bb(dt));
            }
        });
    }

    fn add_assign(ben: &mut Bencher<'_>) {
        let dt = 1.days();
        ben.iter(|| {
            for mut date in representative_dates() {
                date += bb(dt);
                let _ = bb(date);
            }
        });
    }

    fn add_assign_std(ben: &mut Bencher<'_>) {
        let dt = 1.std_days();
        ben.iter(|| {
            for mut date in representative_dates() {
                date += bb(dt);
                let _ = bb(date);
            }
        });
    }

    fn sub(ben: &mut Bencher<'_>) {
        let dt = 5.days();
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date) - bb(dt));
            }
        });
    }

    fn sub_std(ben: &mut Bencher<'_>) {
        let dt = 5.std_days();
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date) - bb(dt));
            }
        });
    }

    fn sub_assign(ben: &mut Bencher<'_>) {
        let dt = 1.days();
        ben.iter(|| {
            for mut date in representative_dates() {
                date -= bb(dt);
                let _ = bb(date);
            }
        });
    }

    fn sub_assign_std(ben: &mut Bencher<'_>) {
        let dt = 1.std_days();
        ben.iter(|| {
            for mut date in representative_dates() {
                date -= bb(dt);
                let _ = bb(date);
            }
        });
    }

    fn sub_self(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates() {
                let _ = bb(bb(date) - bb(date));
            }
        });
    }

    fn partial_ord(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates().windows(2) {
                let first = date[0];
                let second = date[1];
                let _ = bb(bb(first).partial_cmp(&bb(second)));
            }
        });
    }

    fn ord(ben: &mut Bencher<'_>) {
        ben.iter(|| {
            for date in representative_dates().windows(2) {
                let first = date[0];
                let second = date[1];
                let _ = bb(bb(first).cmp(&bb(second)));
            }
        });
    }
}
