/*
 * Copyright (c) 2021-2025, Tim Flynn <trflynn89@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intl/DateTimeFormat.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <math.h>

namespace JS::Intl {

GC_DEFINE_ALLOCATOR(DateTimeFormat);

// 11 DateTimeFormat Objects, https://tc39.es/ecma402/#datetimeformat-objects
DateTimeFormat::DateTimeFormat(Object& prototype)
    : Object(ConstructWithPrototypeTag::Tag, prototype)
{
}

void DateTimeFormat::visit_edges(Cell::Visitor& visitor)
{
    Base::visit_edges(visitor);
    visitor.visit(m_bound_format);
}

static Optional<Unicode::DateTimeFormat const&> get_or_create_formatter(StringView locale, StringView time_zone, OwnPtr<Unicode::DateTimeFormat>& formatter, Optional<Unicode::CalendarPattern> const& format)
{
    if (formatter)
        return *formatter;
    if (!format.has_value())
        return {};

    formatter = Unicode::DateTimeFormat::create_for_pattern_options(locale, time_zone, *format);
    return *formatter;
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format);
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_year_month_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format);
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_month_day_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format);
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_time_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format);
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_plain_date_time_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format);
}

Optional<Unicode::DateTimeFormat const&> DateTimeFormat::temporal_instant_formatter()
{
    return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format);
}

// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern
// 15.9.4 FormatDateTimePattern ( dateTimeFormat, format, pattern, x, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-formatdatetimepattern
Vector<Unicode::DateTimeFormat::Partition> format_date_time_pattern(ValueFormat const& format_record)
{
    return format_record.formatter.format_to_parts(format_record.epoch_milliseconds);
}

// 11.5.6 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-partitiondatetimepattern
// 15.9.5 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-partitiondatetimepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time)
{
    // 1. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
    auto format_record = TRY(handle_date_time_value(vm, date_time_format, time));

    // 5. Let result be ? FormatDateTimePattern(dateTimeFormat, format, pattern, xFormatRecord.[[EpochNanoseconds]]).
    return format_date_time_pattern(format_record);
}

// 11.5.7 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime
// 15.9.6 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetime
ThrowCompletionOr<String> format_date_time(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time)
{
    // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
    // 2. Let result be the empty String.
    String result;

    // NOTE: We short-circuit PartitionDateTimePattern as we do not need individual partitions.
    {
        // 1. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
        auto format_record = TRY(handle_date_time_value(vm, date_time_format, time));

        result = format_record.formatter.format(format_record.epoch_milliseconds);
    }

    // 4. Return result.
    return result;
}

// 11.5.8 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts
// 15.9.7 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetimetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time)
{
    auto& realm = *vm.current_realm();

    // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
    auto parts = TRY(partition_date_time_pattern(vm, date_time_format, time));

    // 2. Let result be ! ArrayCreate(0).
    auto result = MUST(Array::create(realm, 0));

    // 3. Let n be 0.
    size_t n = 0;

    // 4. For each Record { [[Type]], [[Value]] } part in parts, do
    for (auto& part : parts) {
        // a. Let O be OrdinaryObjectCreate(%Object.prototype%).
        auto object = Object::create(realm, realm.intrinsics().object_prototype());

        // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
        MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));

        // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
        MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));

        // d. Perform ! CreateDataProperty(result, ! ToString(n), O).
        MUST(result->create_data_property_or_throw(n, object));

        // e. Increment n by 1.
        ++n;
    }

    // 5. Return result.
    return result;
}

// 11.5.9 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
// 15.9.8 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-partitiondatetimerangepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
    // 1. If IsTemporalObject(x) is true or IsTemporalObject(y) is true, then
    if (is_temporal_object(start) || is_temporal_object(end)) {
        // a. If SameTemporalType(x, y) is false, throw a TypeError exception.
        if (!same_temporal_type(start, end))
            return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatRangeTypeMismatch);
    }

    // 2. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
    auto start_format_record = TRY(handle_date_time_value(vm, date_time_format, start));

    // 3. Let yFormatRecord be ? HandleDateTimeValue(dateTimeFormat, y).
    auto end_format_record = TRY(handle_date_time_value(vm, date_time_format, end));

    return start_format_record.formatter.format_range_to_parts(start_format_record.epoch_milliseconds, end_format_record.epoch_milliseconds);
}

// 11.5.10 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange
// 15.9.9 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerange
ThrowCompletionOr<String> format_date_time_range(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
    // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
    // 2. Let result be the empty String.
    String result;

    // NOTE: We short-circuit PartitionDateTimeRangePattern as we do not need individual partitions.
    {
        // 1. If IsTemporalObject(x) is true or IsTemporalObject(y) is true, then
        if (is_temporal_object(start) || is_temporal_object(end)) {
            // a. If SameTemporalType(x, y) is false, throw a TypeError exception.
            if (!same_temporal_type(start, end))
                return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatRangeTypeMismatch);
        }

        // 2. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
        auto start_format_record = TRY(handle_date_time_value(vm, date_time_format, start));

        // 3. Let yFormatRecord be ? HandleDateTimeValue(dateTimeFormat, y).
        auto end_format_record = TRY(handle_date_time_value(vm, date_time_format, end));

        result = start_format_record.formatter.format_range(start_format_record.epoch_milliseconds, end_format_record.epoch_milliseconds);
    }

    // 4. Return result.
    return result;
}

// 11.5.11 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
// 15.9.10 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerangetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
    auto& realm = *vm.current_realm();

    // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
    auto parts = TRY(partition_date_time_range_pattern(vm, date_time_format, start, end));

    // 2. Let result be ! ArrayCreate(0).
    auto result = MUST(Array::create(realm, 0));

    // 3. Let n be 0.
    size_t n = 0;

    // 4. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
    for (auto& part : parts) {
        // a. Let O be OrdinaryObjectCreate(%ObjectPrototype%).
        auto object = Object::create(realm, realm.intrinsics().object_prototype());

        // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
        MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));

        // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
        MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));

        // d. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
        MUST(object->create_data_property_or_throw(vm.names.source, PrimitiveString::create(vm, part.source)));

        // e. Perform ! CreateDataProperty(result, ! ToString(n), O).
        MUST(result->create_data_property_or_throw(n, object));

        // f. Increment n by 1.
        ++n;
    }

    // 5. Return result.
    return result;
}

// 15.9.1 GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit ), https://tc39.es/proposal-temporal/#sec-getdatetimeformat
Optional<Unicode::CalendarPattern> get_date_time_format(Unicode::CalendarPattern const& options, OptionRequired required, OptionDefaults defaults, OptionInherit inherit)
{
    using enum Unicode::CalendarPattern::Field;

    auto required_options = [&]() -> ReadonlySpan<Unicode::CalendarPattern::Field> {
        static constexpr auto date_fields = AK::Array { Weekday, Year, Month, Day };
        static constexpr auto time_fields = AK::Array { DayPeriod, Hour, Minute, Second, FractionalSecondDigits };
        static constexpr auto year_month_fields = AK::Array { Year, Month };
        static constexpr auto month_day_fields = AK::Array { Month, Day };
        static constexpr auto any_fields = AK::Array { Weekday, Year, Month, Day, DayPeriod, Hour, Minute, Second, FractionalSecondDigits };

        switch (required) {
        // 1. If required is DATE, then
        case OptionRequired::Date:
            // a. Let requiredOptions be « "weekday", "year", "month", "day" ».
            return date_fields;
        // 2. Else if required is TIME, then
        case OptionRequired::Time:
            // a. Let requiredOptions be « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" ».
            return time_fields;
        // 3. Else if required is YEAR-MONTH, then
        case OptionRequired::YearMonth:
            // a. Let requiredOptions be « "year", "month" ».
            return year_month_fields;
        // 4. Else if required is MONTH-DAY, then
        case OptionRequired::MonthDay:
            // a. Let requiredOptions be « "month", "day" ».
            return month_day_fields;
        // 5. Else,
        case OptionRequired::Any:
            // a. Assert: required is ANY.
            // b. Let requiredOptions be « "weekday", "year", "month", "day", "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" ».
            return any_fields;
        }
        VERIFY_NOT_REACHED();
    }();

    auto default_options = [&]() -> ReadonlySpan<Unicode::CalendarPattern::Field> {
        static constexpr auto date_fields = AK::Array { Year, Month, Day };
        static constexpr auto time_fields = AK::Array { Hour, Minute, Second };
        static constexpr auto year_month_fields = AK::Array { Year, Month };
        static constexpr auto month_day_fields = AK::Array { Month, Day };
        static constexpr auto all_fields = AK::Array { Year, Month, Day, Hour, Minute, Second };

        switch (defaults) {
        // 6. If defaults is DATE, then
        case OptionDefaults::Date:
            // a. Let defaultOptions be « "year", "month", "day" ».
            return date_fields;
        // 7. Else if defaults is TIME, then
        case OptionDefaults::Time:
            // a. Let defaultOptions be « "hour", "minute", "second" ».
            return time_fields;
        // 8. Else if defaults is YEAR-MONTH, then
        case OptionDefaults::YearMonth:
            // a. Let defaultOptions be « "year", "month" ».
            return year_month_fields;
        // 9. Else if defaults is MONTH-DAY, then
        case OptionDefaults::MonthDay:
            // a. Let defaultOptions be « "month", "day" ».
            return month_day_fields;
        // 10. Else,
        case OptionDefaults::ZonedDateTime:
        case OptionDefaults::All:
            // a. Assert: defaults is ZONED-DATE-TIME or ALL.
            // b. Let defaultOptions be « "year", "month", "day", "hour", "minute", "second" ».
            return all_fields;
        }
        VERIFY_NOT_REACHED();
    }();

    Unicode::CalendarPattern format_options {};

    // 11. If inherit is ALL, then
    if (inherit == OptionInherit::All) {
        // a. Let formatOptions be a copy of options.
        format_options = options;
    }
    // 12. Else,
    else {
        // a. Let formatOptions be a new Record.

        // b. If required is one of DATE, YEAR-MONTH, or ANY, then
        if (required == OptionRequired::Date || required == OptionRequired::YearMonth || required == OptionRequired::Any) {
            // i. Set formatOptions.[[era]] to options.[[era]].
            format_options.era = options.era;
        }

        // c. If required is TIME or ANY, then
        if (required == OptionRequired::Time || required == OptionRequired::Any) {
            // i. Set formatOptions.[[hourCycle]] to options.[[hourCycle]].
            format_options.hour_cycle = options.hour_cycle;
            format_options.hour12 = options.hour12;
        }
    }

    // 13. Let anyPresent be false.
    auto any_present = false;

    // 14. For each property name prop of « "weekday", "year", "month", "day", "era", "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do
    static constexpr auto all_fields = AK::Array { Weekday, Year, Month, Day, Era, DayPeriod, Hour, Minute, Second, FractionalSecondDigits };

    options.for_each_calendar_field_zipped_with(format_options, all_fields, [&](auto const& option, auto&) {
        // a. If options.[[<prop>]] is not undefined, set anyPresent to true.
        if (option.has_value()) {
            any_present = true;
            return IterationDecision::Break;
        }

        return IterationDecision::Continue;
    });

    // 15. Let needDefaults be true.
    auto need_defaults = true;

    // 16. For each property name prop of requiredOptions, do
    options.for_each_calendar_field_zipped_with(format_options, required_options, [&](auto const& option, auto& format_option) {
        // a. Let value be options.[[<prop>]].
        // b. If value is not undefined, then
        if (option.has_value()) {
            // i. Set formatOptions.[[<prop>]] to value.
            format_option = *option;

            // ii. Set needDefaults to false.
            need_defaults = false;
        }

        return IterationDecision::Continue;
    });

    // 17. If needDefaults is true, then
    if (need_defaults) {
        // a. If anyPresent is true and inherit is RELEVANT, return null.
        if (any_present && inherit == OptionInherit::Relevant)
            return {};

        // b. For each property name prop of defaultOptions, do
        options.for_each_calendar_field_zipped_with(format_options, default_options, [&](auto const&, auto& format_option) {
            using ValueType = typename RemoveCVReference<decltype(format_option)>::ValueType;

            if constexpr (IsSame<ValueType, Unicode::CalendarPatternStyle>) {
                // i. Set formatOptions.[[<prop>]] to "numeric".
                format_option = Unicode::CalendarPatternStyle::Numeric;
            }

            return IterationDecision::Continue;
        });

        // c. If defaults is ZONED-DATE-TIME and formatOptions.[[timeZoneName]] is undefined, then
        if (defaults == OptionDefaults::ZonedDateTime && !format_options.time_zone_name.has_value()) {
            // i. Set formatOptions.[[timeZoneName]] to "short".
            format_options.time_zone_name = Unicode::CalendarPatternStyle::Short;
        }
    }

    // 18. If matcher is "basic", then
    //     a. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
    // 19. Else,
    //     a. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
    // 20. Return bestFormat.
    return format_options;
}

// 15.9.2 AdjustDateTimeStyleFormat ( formats, baseFormat, matcher, allowedOptions ), https://tc39.es/proposal-temporal/#sec-adjustdatetimestyleformat
Unicode::CalendarPattern adjust_date_time_style_format(Unicode::CalendarPattern const& base_format, ReadonlySpan<Unicode::CalendarPattern::Field> allowed_options)
{
    // 1. Let formatOptions be a new Record.
    Unicode::CalendarPattern format_options;

    // 2. For each field name fieldName of allowedOptions, do
    base_format.for_each_calendar_field_zipped_with(format_options, allowed_options, [&](auto const& base_option, auto& format_option) {
        // a. Set the field of formatOptions whose name is fieldName to the value of the field of baseFormat whose name is fieldName.
        format_option = base_option;
        return IterationDecision::Continue;
    });

    // 3. If matcher is "basic", then
    //     a. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
    // 4. Else,
    //     a. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
    // 5. Return bestFormat.
    return format_options;
}

// 15.9.11 ToDateTimeFormattable ( value ), https://tc39.es/proposal-temporal/#sec-todatetimeformattable
ThrowCompletionOr<FormattableDateTime> to_date_time_formattable(VM& vm, Value value)
{
    // 1. If IsTemporalObject(value) is true, return value.
    if (value.is_object()) {
        auto& object = value.as_object();

        if (is<Temporal::Instant>(object))
            return FormattableDateTime { static_cast<Temporal::Instant&>(object) };
        if (is<Temporal::PlainDate>(object))
            return FormattableDateTime { static_cast<Temporal::PlainDate&>(object) };
        if (is<Temporal::PlainDateTime>(object))
            return FormattableDateTime { static_cast<Temporal::PlainDateTime&>(object) };
        if (is<Temporal::PlainMonthDay>(object))
            return FormattableDateTime { static_cast<Temporal::PlainMonthDay&>(object) };
        if (is<Temporal::PlainTime>(object))
            return FormattableDateTime { static_cast<Temporal::PlainTime&>(object) };
        if (is<Temporal::PlainYearMonth>(object))
            return FormattableDateTime { static_cast<Temporal::PlainYearMonth&>(object) };
        if (is<Temporal::ZonedDateTime>(object))
            return FormattableDateTime { static_cast<Temporal::ZonedDateTime&>(object) };
    }

    // 2. Return ? ToNumber(value).
    return FormattableDateTime { TRY(value.to_number(vm)).as_double() };
}

// 15.9.12 IsTemporalObject ( value ), https://tc39.es/proposal-temporal/#sec-temporal-istemporalobject
bool is_temporal_object(FormattableDateTime const& value)
{
    // 1. If value is not an Object, then
    //     a. Return false.
    // 2. If value does not have an [[InitializedTemporalDate]], [[InitializedTemporalTime]], [[InitializedTemporalDateTime]],
    //    [[InitializedTemporalZonedDateTime]], [[InitializedTemporalYearMonth]], [[InitializedTemporalMonthDay]], or
    //    [[InitializedTemporalInstant]] internal slot, then
    //     a. Return false.
    // 3. Return true.
    return !value.has<double>();
}

// 15.9.13 SameTemporalType ( x, y ), https://tc39.es/proposal-temporal/#sec-temporal-istemporalobject
bool same_temporal_type(FormattableDateTime const& x, FormattableDateTime const& y)
{
    // 1. If either of IsTemporalObject(x) or IsTemporalObject(y) is false, return false.
    if (!is_temporal_object(x) || !is_temporal_object(y))
        return false;

    // 2. If x has an [[InitializedTemporalDate]] internal slot and y does not, return false.
    // 3. If x has an [[InitializedTemporalTime]] internal slot and y does not, return false.
    // 4. If x has an [[InitializedTemporalDateTime]] internal slot and y does not, return false.
    // 5. If x has an [[InitializedTemporalZonedDateTime]] internal slot and y does not, return false.
    // 6. If x has an [[InitializedTemporalYearMonth]] internal slot and y does not, return false.
    // 7. If x has an [[InitializedTemporalMonthDay]] internal slot and y does not, return false.
    // 8. If x has an [[InitializedTemporalInstant]] internal slot and y does not, return false.
    // 9. Return true.
    return x.index() == y.index();
}

static double to_epoch_milliseconds(Crypto::SignedBigInteger const& epoch_nanoseconds)
{
    return Temporal::big_floor(epoch_nanoseconds, Temporal::NANOSECONDS_PER_MILLISECOND).to_double();
}

// 15.9.15 HandleDateTimeTemporalDate ( dateTimeFormat, temporalDate ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_date(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainDate const& temporal_date)
{
    // 1. If temporalDate.[[Calendar]] is not dateTimeFormat.[[Calendar]] or "iso8601", throw a RangeError exception.
    if (!temporal_date.calendar().is_one_of(date_time_format.calendar(), "iso8601"sv))
        return vm.throw_completion<RangeError>(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainDate"sv, temporal_date.calendar(), date_time_format.calendar());

    // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalDate.[[ISODate]], NoonTimeRecord()).
    auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_date.iso_date(), Temporal::noon_time_record());

    // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE).
    auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible));

    // 4. Let format be dateTimeFormat.[[TemporalPlainDateFormat]].
    auto formatter = date_time_format.temporal_plain_date_formatter();

    // 5. If format is null, throw a TypeError exception.
    if (!formatter.has_value())
        return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainDate"sv);

    // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) };
}

// 15.9.16 HandleDateTimeTemporalYearMonth ( dateTimeFormat, temporalYearMonth ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_year_month(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainYearMonth const& temporal_year_month)
{
    // 1. If temporalYearMonth.[[Calendar]] is not equal to dateTimeFormat.[[Calendar]], then
    if (temporal_year_month.calendar() != date_time_format.calendar()) {
        // a. Throw a RangeError exception.
        return vm.throw_completion<RangeError>(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainYearMonth"sv, temporal_year_month.calendar(), date_time_format.calendar());
    }

    // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalYearMonth.[[ISODate]], NoonTimeRecord()).
    auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_year_month.iso_date(), Temporal::noon_time_record());

    // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE).
    auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible));

    // 4. Let format be dateTimeFormat.[[TemporalPlainYearMonthFormat]].
    auto formatter = date_time_format.temporal_plain_year_month_formatter();

    // 5. If format is null, throw a TypeError exception.
    if (!formatter.has_value())
        return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainYearMonth"sv);

    // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) };
}

// 15.9.17 HandleDateTimeTemporalMonthDay ( dateTimeFormat, temporalMonthDay ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_month_day(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainMonthDay const& temporal_month_day)
{
    // 1. If temporalMonthDay.[[Calendar]] is not equal to dateTimeFormat.[[Calendar]], then
    if (temporal_month_day.calendar() != date_time_format.calendar()) {
        // a. Throw a RangeError exception.
        return vm.throw_completion<RangeError>(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainMonthDay"sv, temporal_month_day.calendar(), date_time_format.calendar());
    }

    // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalMonthDay.[[ISODate]], NoonTimeRecord()).
    auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_month_day.iso_date(), Temporal::noon_time_record());

    // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE).
    auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible));

    // 4. Let format be dateTimeFormat.[[TemporalPlainMonthDayFormat]].
    auto formatter = date_time_format.temporal_plain_month_day_formatter();

    // 5. If format is null, throw a TypeError exception.
    if (!formatter.has_value())
        return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainMonthDay"sv);

    // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) };
}

// 15.9.18 HandleDateTimeTemporalTime ( dateTimeFormat, temporalTime ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_time(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainTime const& temporal_time)
{
    // 1. Let isoDate be CreateISODateRecord(1970, 1, 1).
    auto iso_date = Temporal::create_iso_date_record(1970, 1, 1);

    // 2. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, temporalTime.[[Time]]).
    auto iso_date_time = Temporal::combine_iso_date_and_time_record(iso_date, temporal_time.time());

    // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE).
    auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible));

    // 4. Let format be dateTimeFormat.[[TemporalPlainTimeFormat]].
    auto formatter = date_time_format.temporal_plain_time_formatter();

    // 5. If format is null, throw a TypeError exception.
    if (!formatter.has_value())
        return vm.throw_completion<TypeError>(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainTime"sv);

    // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) };
}

// 15.9.19 HandleDateTimeTemporalDateTime ( dateTimeFormat, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldatetime
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_date_time(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainDateTime const& date_time)
{
    // 1. If dateTime.[[Calendar]] is not "iso8601" and not equal to dateTimeFormat.[[Calendar]], then
    if (!date_time.calendar().is_one_of(date_time_format.calendar(), "iso8601"sv)) {
        // a. Throw a RangeError exception.
        return vm.throw_completion<RangeError>(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainDateTime"sv, date_time.calendar(), date_time_format.calendar());
    }

    // 2. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], dateTime.[[ISODateTime]], COMPATIBLE).
    auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), date_time.iso_date_time(), Temporal::Disambiguation::Compatible));

    // 3. Let format be dateTimeFormat.[[TemporalPlainDateTimeFormat]].
    auto formatter = date_time_format.temporal_plain_date_time_formatter();
    VERIFY(formatter.has_value());

    // 4. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) };
}

// 15.9.20 HandleDateTimeTemporalInstant ( dateTimeFormat, instant ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalinstant
ValueFormat handle_date_time_temporal_instant(DateTimeFormat& date_time_format, Temporal::Instant const& instant)
{
    // 1. Let format be dateTimeFormat.[[TemporalInstantFormat]].
    auto formatter = date_time_format.temporal_instant_formatter();
    VERIFY(formatter.has_value());

    // 2. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: instant.[[EpochNanoseconds]]  }.
    return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(instant.epoch_nanoseconds()->big_integer()) };
}

// 15.9.21 HandleDateTimeOthers ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimeothers
ThrowCompletionOr<ValueFormat> handle_date_time_others(VM& vm, DateTimeFormat& date_time_format, double time)
{
    // 1. Set x to TimeClip(x).
    time = time_clip(time);

    // 2. If x is NaN, throw a RangeError exception.
    if (isnan(time))
        return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);

    // 3. Let epochNanoseconds be ℤ(ℝ(x) × 10**6).

    // 4. Let format be dateTimeFormat.[[DateTimeFormat]].
    auto const& formatter = date_time_format.formatter();

    // 5. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNanoseconds  }.
    return ValueFormat { .formatter = formatter, .epoch_milliseconds = time };
}

// 15.9.22 HandleDateTimeValue ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue
ThrowCompletionOr<ValueFormat> handle_date_time_value(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& formattable)
{
    return formattable.visit(
        // 1. If x is an Object, then
        // a. If x has an [[InitializedTemporalDate]] internal slot, then
        [&](GC::Ref<Temporal::PlainDate> temporal_date) {
            // i. Return ? HandleDateTimeTemporalDate(dateTimeFormat, x).
            return handle_date_time_temporal_date(vm, date_time_format, temporal_date);
        },
        // b. If x has an [[InitializedTemporalYearMonth]] internal slot, then
        [&](GC::Ref<Temporal::PlainYearMonth> temporal_year_month) {
            // i. Return ? HandleDateTimeTemporalYearMonth(dateTimeFormat, x).
            return handle_date_time_temporal_year_month(vm, date_time_format, temporal_year_month);
        },
        // c. If x has an [[InitializedTemporalMonthDay]] internal slot, then
        [&](GC::Ref<Temporal::PlainMonthDay> temporal_month_day) {
            // i. Return ? HandleDateTimeTemporalMonthDay(dateTimeFormat, x).
            return handle_date_time_temporal_month_day(vm, date_time_format, temporal_month_day);
        },
        // d. If x has an [[InitializedTemporalTime]] internal slot, then
        [&](GC::Ref<Temporal::PlainTime> temporal_time) {
            // i. Return ? HandleDateTimeTemporalTime(dateTimeFormat, x).
            return handle_date_time_temporal_time(vm, date_time_format, temporal_time);
        },
        // e. If x has an [[InitializedTemporalDateTime]] internal slot, then
        [&](GC::Ref<Temporal::PlainDateTime> date_time) {
            // i. Return ? HandleDateTimeTemporalDateTime(dateTimeFormat, x).
            return handle_date_time_temporal_date_time(vm, date_time_format, date_time);
        },
        // f. If x has an [[InitializedTemporalInstant]] internal slot, then
        [&](GC::Ref<Temporal::Instant> instant) -> ThrowCompletionOr<ValueFormat> {
            // i. Return HandleDateTimeTemporalInstant(dateTimeFormat, x).
            return handle_date_time_temporal_instant(date_time_format, instant);
        },
        // g. Assert: x has an [[InitializedTemporalZonedDateTime]] internal slot.
        [&](GC::Ref<Temporal::ZonedDateTime>) -> ThrowCompletionOr<ValueFormat> {
            // h. Throw a TypeError exception.
            return vm.throw_completion<TypeError>(ErrorType::IntlTemporalZonedDateTime);
        },
        // 2. Return ? HandleDateTimeOthers(dateTimeFormat, x).
        [&](double time) {
            return handle_date_time_others(vm, date_time_format, time);
        });
}

}
