import { CanonicalizeUValue } from '../../ecma402/not-implemented.mts';
import { __ts_cast__, isArray, type Mutable } from '../../helpers.mts';
import { ParseMonthCode, ParseTemporalCalendarString } from '../../parser/TemporalParser.mts';
import { isTemporalPlainDateTimeObject } from '../../intrinsics/Temporal/PlainDateTime.mts';
import { isTemporalPlainMonthDayObject } from '../../intrinsics/Temporal/PlainMonthDay.mts';
import { isTemporalZonedDateTimeObject } from '../../intrinsics/Temporal/ZonedDateTime.mts';
import { isTemporalPlainDateObject, type ISODateRecord } from '../../intrinsics/Temporal/PlainDate.mts';
import { isTemporalPlainYearMonthObject } from '../../intrinsics/Temporal/PlainYearMonth.mts';
import { ToZeroPaddedDecimalString } from './addition.mts';
import type { YearWeekRecord } from './addition.mts';
import {
  EpochDaysToEpochMs,
  EpochTimeForYear,
  EpochTimeToDayInYear,
  EpochTimeToWeekDay,
  ISODateToEpochDays,
  MathematicalDaysInYear,
  MathematicalInLeapYear,
  TemporalUnit,
  ToIntegerWithTruncation, ToOffsetString, ToPositiveIntegerWithTruncation, type DateUnit,
} from './temporal.mts';
import { ToTemporalTimeZoneIdentifier } from './time-zone.mts';
import { mark_OtherCalendarNotImplemented, unreachable_OtherCalendarNotImplemented } from './not-implemented.mts';
import {
  AddDaysToISODate,
  Assert,
  BalanceISOYearMonth,
  CompareISODate,
  CreateDateDurationRecord,
  CreateISODateRecord,
  F,
  Get,
  ISODateSurpasses,
  ISODateWithinLimits,
  JSStringValue,
  NumberValue,
  ObjectValue,
  Q,
  R,
  RegulateISODate,
  Throw,
  ToString,
  Value,
  X,
  ZeroDateDuration,
  type DateDurationRecord,
  type PlainCompletion, type PlainEvaluator,
} from '#self';

/** https://tc39.es/proposal-temporal/#sec-calendar-types */
export type CalendarType = 'iso8601';

/** https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar */
export function CanonicalizeCalendar(id: string): PlainCompletion<CalendarType> {
  const calendars = AvailableCalendars();
  if (!calendars.includes(id.toLowerCase() as CalendarType)) {
    return Throw.RangeError('$1 is not a supported calendar', id);
  }
  return CanonicalizeUValue('ca', id) as CalendarType;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-availablecalendars */
export function AvailableCalendars(): CalendarType[] {
  mark_OtherCalendarNotImplemented();
  return ['iso8601'];
}

export type MonthCode = string & { __brand: 'MonthCode' };

/** https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode */
export function CreateMonthCode(monthNumber: number, isLeapMonth: boolean): MonthCode {
  if (!isLeapMonth) Assert(monthNumber > 0);
  const numberPart = ToZeroPaddedDecimalString(monthNumber, 2);
  if (isLeapMonth) {
    return `M${numberPart}L` as MonthCode;
  }
  return `M${numberPart}` as MonthCode;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendar-date-records */
export interface CalendarDateRecord {
  readonly Era: string | undefined;
  readonly EraYear: number | undefined;
  readonly Year: number;
  readonly Month: number;
  readonly MonthCode: string;
  readonly Day: number;
  readonly DayOfWeek: number;
  readonly DayOfYear: number;
  readonly WeekOfYear: YearWeekRecord;
  readonly DaysInWeek: number;
  readonly DaysInMonth: number;
  readonly DaysInYear: number;
  readonly MonthsInYear: number;
  readonly InLeapYear: boolean;
}

/** https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields */
export interface CalendarFieldsRecord {
  readonly Era: string | undefined;
  readonly EraYear: number | undefined;
  Year: number | undefined;
  Month: number | undefined;
  MonthCode: string | undefined;
  Day: number | undefined;
  Hour: number | undefined;
  Minute: number | undefined;
  Second: number | undefined;
  Millisecond: number | undefined;
  Microsecond: number | undefined;
  Nanosecond: number | undefined;
  OffsetString: string | undefined;
  readonly TimeZone: string | undefined;
}

export enum Table19_Conversion {
  ToString = 'to-string',
  ToIntegerWithTruncation = 'to-integer-with-truncation',
  ToPositiveIntegerWithTruncation = 'to-positive-integer-with-truncation',
  ToTemporalTimeZoneIdentifier = 'to-temporal-time-zone-identifier',
  ToMonthCode = 'to-month-code',
  ToOffsetString = 'to-offset-string',
}

export type CalendarFieldsRecordEnumerationKey = 'era' | 'era-year' | 'year' | 'month' | 'month-code' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' | 'offset' | 'time-zone';

export const Table19_CalendarFieldsRecordFields = [
  /* eslint-disable object-curly-newline */
  { FieldName: 'Era', DefaultValue: undefined, PropertyKey: 'era', EnumerationKey: 'era', Conversion: Table19_Conversion.ToString },
  { FieldName: 'EraYear', DefaultValue: undefined, PropertyKey: 'eraYear', EnumerationKey: 'era-year', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Year', DefaultValue: undefined, PropertyKey: 'year', EnumerationKey: 'year', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Month', DefaultValue: undefined, PropertyKey: 'month', EnumerationKey: 'month', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation },
  { FieldName: 'MonthCode', DefaultValue: undefined, PropertyKey: 'monthCode', EnumerationKey: 'month-code', Conversion: Table19_Conversion.ToMonthCode },
  { FieldName: 'Day', DefaultValue: undefined, PropertyKey: 'day', EnumerationKey: 'day', Conversion: Table19_Conversion.ToPositiveIntegerWithTruncation },
  { FieldName: 'Hour', DefaultValue: 0, PropertyKey: 'hour', EnumerationKey: 'hour', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Minute', DefaultValue: 0, PropertyKey: 'minute', EnumerationKey: 'minute', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Second', DefaultValue: 0, PropertyKey: 'second', EnumerationKey: 'second', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Millisecond', DefaultValue: 0, PropertyKey: 'millisecond', EnumerationKey: 'millisecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Microsecond', DefaultValue: 0, PropertyKey: 'microsecond', EnumerationKey: 'microsecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'Nanosecond', DefaultValue: 0, PropertyKey: 'nanosecond', EnumerationKey: 'nanosecond', Conversion: Table19_Conversion.ToIntegerWithTruncation },
  { FieldName: 'OffsetString', DefaultValue: undefined, PropertyKey: 'offsetString', EnumerationKey: 'offset', Conversion: Table19_Conversion.ToOffsetString },
  { FieldName: 'TimeZone', DefaultValue: undefined, PropertyKey: 'timeZone', EnumerationKey: 'time-zone', Conversion: Table19_Conversion.ToTemporalTimeZoneIdentifier },
  /* eslint-enable object-curly-newline */
] as const satisfies {
  FieldName: keyof CalendarFieldsRecord;
  DefaultValue: string | number | undefined;
  PropertyKey: string;
  EnumerationKey: CalendarFieldsRecordEnumerationKey;
  Conversion: Table19_Conversion;
}[];

/** https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields */
export function* PrepareCalendarFields(
  calendar: CalendarType,
  fields: ObjectValue,
  calendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[],
  nonCalendarFieldNames: readonly CalendarFieldsRecordEnumerationKey[],
  requiredFieldNames: 'partial' | readonly CalendarFieldsRecordEnumerationKey[],
): PlainEvaluator<CalendarFieldsRecord> {
  // Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of calendarFieldNames and nonCalendarFieldNames.
  if (isArray(requiredFieldNames)) {
    Assert(calendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1));
    Assert(nonCalendarFieldNames.every((name) => requiredFieldNames.filter((requiredName) => name === requiredName).length <= 1));
  }
  let fieldNames: CalendarFieldsRecordEnumerationKey[] = [...calendarFieldNames, ...nonCalendarFieldNames];
  const extraFieldNames = CalendarExtraFields(calendar, calendarFieldNames);
  fieldNames = [...fieldNames, ...extraFieldNames];
  // Assert: fieldNames contains no duplicate elements.
  Assert(fieldNames.length === new Set(fieldNames).size);
  const result: Mutable<CalendarFieldsRecord> = {
    Era: undefined,
    EraYear: undefined,
    Year: undefined,
    Month: undefined,
    MonthCode: undefined,
    Day: undefined,
    Hour: undefined,
    Minute: undefined,
    Second: undefined,
    Millisecond: undefined,
    Microsecond: undefined,
    Nanosecond: undefined,
    OffsetString: undefined,
    TimeZone: undefined,
  };
  let any = false;

  // Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19 corresponding to the elements of fieldNames, sorted according to lexicographic code unit order.
  const sortedPropertyNames = [...Table19_CalendarFieldsRecordFields].sort((a, b) => (a.PropertyKey < b.PropertyKey ? -1 : 1));

  for (const {
    FieldName, PropertyKey, Conversion, DefaultValue, EnumerationKey,
  } of sortedPropertyNames) {
    __ts_cast__<keyof CalendarFieldsRecord>(FieldName);
    // Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
    const key = EnumerationKey;
    let value = Q(yield* Get(fields, Value(PropertyKey)));

    if (value !== Value.undefined) {
      any = true;

      if (Conversion === Table19_Conversion.ToIntegerWithTruncation) {
        value = F(Q(yield* ToIntegerWithTruncation(value)));
      } else if (Conversion === Table19_Conversion.ToPositiveIntegerWithTruncation) {
        value = F(Q(yield* ToPositiveIntegerWithTruncation(value)));
      } else if (Conversion === Table19_Conversion.ToString) {
        value = Q(yield* ToString(value));
      } else if (Conversion === Table19_Conversion.ToTemporalTimeZoneIdentifier) {
        value = Value(Q(ToTemporalTimeZoneIdentifier(value)));
      } else if (Conversion === Table19_Conversion.ToMonthCode) {
        const parsed = Q(yield* ParseMonthCode(value));
        value = Value(CreateMonthCode(parsed.MonthNumber, parsed.IsLeapMonth));
      } else {
        Assert(Conversion === Table19_Conversion.ToOffsetString);
        value = Value(Q(yield* ToOffsetString(value)));
      }

      let assignValue;
      if (value instanceof NumberValue) {
        assignValue = R(value);
      } else if (value instanceof JSStringValue) {
        assignValue = value.stringValue();
      }
      if (assignValue === undefined) {
        throw new Error('invalid type');
      }
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      result[FieldName] = assignValue as any;
    } else if (isArray(requiredFieldNames)) {
      if (requiredFieldNames.includes(key)) {
        return Throw.TypeError('$1 is a required on object $2', key, fields);
      }
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      result[FieldName] = DefaultValue as any;
    }
  }

  if (requiredFieldNames === 'partial' && !any) {
    return Throw.TypeError('$1 is not a TemporalTimeLike object', fields);
  }
  return result;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent */
export function CalendarFieldKeysPresent(fields: CalendarFieldsRecord): CalendarFieldsRecordEnumerationKey[] {
  const list: CalendarFieldsRecordEnumerationKey[] = [];
  for (const { FieldName, EnumerationKey } of Table19_CalendarFieldsRecordFields) {
    const value = fields[FieldName];
    const enumerationKey = EnumerationKey;
    if (value !== undefined) {
      list.push(enumerationKey);
    }
  }
  return list;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields */
export function CalendarMergeFields(calendar: CalendarType, fields: CalendarFieldsRecord, additionalFields: CalendarFieldsRecord): CalendarFieldsRecord {
  const additionalKeys = CalendarFieldKeysPresent(additionalFields);
  const overriddenKeys = CalendarFieldKeysToIgnore(calendar, additionalKeys);
  const merged: Mutable<CalendarFieldsRecord> = {
    Era: undefined,
    EraYear: undefined,
    Year: undefined,
    Month: undefined,
    MonthCode: undefined,
    Day: undefined,
    Hour: undefined,
    Minute: undefined,
    Second: undefined,
    Millisecond: undefined,
    Microsecond: undefined,
    Nanosecond: undefined,
    OffsetString: undefined,
    TimeZone: undefined,
  };
  const fieldsKeys = CalendarFieldKeysPresent(fields);
  for (const { EnumerationKey, FieldName } of Table19_CalendarFieldsRecordFields) {
    const key = EnumerationKey;
    if (fieldsKeys.includes(key) && !overriddenKeys.includes(key)) {
      const propValue = fields[FieldName];
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      merged[FieldName] = propValue as any;
    }
    if (additionalKeys.includes(key)) {
      const propValue = additionalFields[FieldName];
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      merged[FieldName] = propValue as any;
    }
  }
  return merged;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateadd */
export function NonISODateAdd(
  _calendar: CalendarType,
  _isoDate: ISODateRecord,
  _duration: DateDurationRecord,
  _overflow: 'constrain' | 'reject',
): never {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd */
export function CalendarDateAdd(
  calendar: CalendarType,
  isoDate: ISODateRecord,
  duration: DateDurationRecord,
  overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
  let result: ISODateRecord;
  if (calendar === 'iso8601') {
    const intermediate = Q(BalanceISOYearMonth(isoDate.Year + duration.Years, isoDate.Month + duration.Months));
    const regulated = Q(RegulateISODate(intermediate.Year, intermediate.Month, isoDate.Day, overflow));
    const days = regulated.Day + duration.Days + 7 * duration.Weeks;
    result = Q(AddDaysToISODate(regulated, days));
  } else {
    result = Q(NonISODateAdd(calendar, isoDate, duration, overflow));
  }
  if (!ISODateWithinLimits(result)) {
    return Throw.RangeError('Resulting ISODate is out of range');
  }
  return result;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisodateuntil */
export function NonISODateUntil(
  _calendar: CalendarType,
  _one: ISODateRecord,
  _two: ISODateRecord,
  _largestUnit: DateUnit,
): never {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil */
export function CalendarDateUntil(
  calendar: CalendarType,
  one: ISODateRecord,
  two: ISODateRecord,
  largestUnit: DateUnit,
): DateDurationRecord {
  if (calendar === 'iso8601') {
    const sign = -CompareISODate(one, two) as 1 | -1 | 0;
    if (sign === 0) {
      return ZeroDateDuration();
    }
    let years = 0;
    if (largestUnit === TemporalUnit.Year) {
      let candidateYears = sign;
      while (!ISODateSurpasses(sign, one, two, candidateYears, 0, 0, 0)) {
        years = candidateYears;
        candidateYears += sign;
      }
    }
    let months = 0;
    if (largestUnit === TemporalUnit.Month) {
      let candidateMonths = sign;
      while (!ISODateSurpasses(sign, one, two, years, candidateMonths, 0, 0)) {
        months = candidateMonths;
        candidateMonths += sign;
      }
    }
    let weeks = 0;
    if (largestUnit === TemporalUnit.Week) {
      let candidateWeeks = sign;
      while (!ISODateSurpasses(sign, one, two, years, months, candidateWeeks, 0)) {
        weeks = candidateWeeks;
        candidateWeeks += sign;
      }
    }
    let days = 0;
    let candidateDays = sign;
    while (!ISODateSurpasses(sign, one, two, years, months, weeks, candidateDays)) {
      days = candidateDays;
      candidateDays += sign;
    }
    return X(CreateDateDurationRecord(years, months, weeks, days));
  }
  return NonISODateUntil(calendar, one, two, largestUnit);
}

/** https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier */
export function ToTemporalCalendarIdentifier(temporalCalendarLike: Value): PlainCompletion<CalendarType> {
  if (temporalCalendarLike instanceof ObjectValue) {
    if (
      isTemporalPlainDateObject(temporalCalendarLike)
      || isTemporalPlainDateTimeObject(temporalCalendarLike)
      || isTemporalPlainMonthDayObject(temporalCalendarLike)
      || isTemporalPlainYearMonthObject(temporalCalendarLike)
      || isTemporalZonedDateTimeObject(temporalCalendarLike)) {
      return temporalCalendarLike.Calendar;
    }
  }
  if (!(temporalCalendarLike instanceof JSStringValue)) {
    return Throw.TypeError('temporalCalendarLike must be a string or a Temporal object, but got $1', temporalCalendarLike);
  }
  const identifier = Q(ParseTemporalCalendarString(temporalCalendarLike.stringValue()));
  return Q(CanonicalizeCalendar(identifier));
}

/** https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendaridentifierwithisodefault */
export function* GetTemporalCalendarIdentifierWithISODefault(item: ObjectValue): PlainEvaluator<CalendarType> {
  if (isTemporalPlainDateObject(item)
    || isTemporalPlainDateTimeObject(item)
    || isTemporalPlainMonthDayObject(item)
    || isTemporalPlainYearMonthObject(item)
    || isTemporalZonedDateTimeObject(item)) {
    return item.Calendar;
  }
  const calendarLike = Q(yield* Get(item, Value('calendar')));
  if (calendarLike === Value.undefined) {
    return 'iso8601';
  }
  return Q(ToTemporalCalendarIdentifier(calendarLike));
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields */
export function* CalendarDateFromFields(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
  Q(yield* CalendarResolveFields(calendar, fields, 'date'));
  const result = Q(CalendarDateToISO(calendar, fields, overflow));
  if (!ISODateWithinLimits(result)) {
    return Throw.RangeError('Resulting ISODate is out of range');
  }
  return result;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields */
export function* CalendarYearMonthFromFields(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
  Q(yield* CalendarResolveFields(calendar, fields, 'year-month'));
  // Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the month's first day is skipped by this calendar.)
  const firstDayIndex = 1;
  fields.Day = firstDayIndex;
  const result = Q(CalendarDateToISO(calendar, fields, overflow));
  if (!ISODateWithinLimits(result)) {
    return Throw.RangeError('Resulting ISODate is out of range');
  }
  return result;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields */
export function* CalendarMonthDayFromFields(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  overflow: 'constrain' | 'reject',
): PlainEvaluator<ISODateRecord> {
  Q(yield* CalendarResolveFields(calendar, fields, 'month-day'));
  const result = Q(CalendarMonthDayToISOReferenceDate(calendar, fields, overflow));
  if (!ISODateWithinLimits(result)) {
    return Throw.RangeError('Resulting ISODate is out of range');
  }
  return result;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation */
export function FormatCalendarAnnotation(
  id: CalendarType,
  showCalendar: 'auto' | 'always' | 'never' | 'critical',
): string {
  if (showCalendar === 'never') {
    return '';
  }
  if (showCalendar === 'auto' && id === 'iso8601') {
    return '';
  }
  const flag = showCalendar === 'critical' ? '!' : '';
  return `[${flag}u-ca=${id}]`;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarequals */
export function CalendarEquals(one: CalendarType, two: CalendarType): boolean {
  if (CanonicalizeUValue('ca', one) === CanonicalizeUValue('ca', two)) {
    return true;
  }
  return false;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth */
export function ISODaysInMonth(year: number, month: number): number {
  if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) {
    return 31;
  }
  if (month === 4 || month === 6 || month === 9 || month === 11) {
    return 30;
  }
  Assert(month === 2);
  return 28 + MathematicalInLeapYear(EpochTimeForYear(year));
}

/** https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear */
export function ISOWeekOfYear(isoDate: ISODateRecord): YearWeekRecord {
  const year = isoDate.Year;
  const wednesday = 3;
  const thursday = 4;
  const friday = 5;
  const saturday = 6;
  const daysInWeek = 7;
  const maxWeekNumber = 53;
  const dayOfYear = ISODayOfYear(isoDate);
  const dayOfWeek = ISODayOfWeek(isoDate);
  const week = Math.floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek);
  if (week < 1) {
    // NOTE: This is the last week of the previous year.
    const jan1st = CreateISODateRecord(year, 1, 1);
    const dayOfJan1st = ISODayOfWeek(jan1st);
    if (dayOfJan1st === friday) {
      return { Week: maxWeekNumber, Year: year - 1 };
    }
    if (dayOfJan1st === saturday && MathematicalInLeapYear(EpochTimeForYear(year - 1)) === 1) {
      return { Week: maxWeekNumber, Year: year - 1 };
    }
    return { Week: maxWeekNumber - 1, Year: year - 1 };
  }
  if (week === maxWeekNumber) {
    const daysInYear = MathematicalDaysInYear(year);
    const daysLaterInYear = daysInYear - dayOfYear;
    const daysAfterThursday = thursday - dayOfWeek;
    if (daysLaterInYear < daysAfterThursday) {
      return { Week: 1, Year: year + 1 };
    }
  }
  return { Week: week, Year: year };
}

/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear */
export function ISODayOfYear(isoDate: ISODateRecord): number {
  const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day);
  return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek */
export function ISODayOfWeek(isoDate: ISODateRecord): number {
  const epochDays = ISODateToEpochDays(isoDate.Year, isoDate.Month - 1, isoDate.Day);
  const dayOfWeek = EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0));
  if (dayOfWeek === 0) {
    return 7;
  }
  return dayOfWeek;
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendardatetoiso */
export function NonISOCalendarDateToISO(
  _calendar: CalendarType,
  _fields: CalendarFieldsRecord,
  _overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso */
export function CalendarDateToISO(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
  if (calendar === 'iso8601') {
    Assert(fields.Year !== undefined && fields.Month !== undefined && fields.Day !== undefined);
    return Q(RegulateISODate(fields.Year, fields.Month, fields.Day, overflow));
  }
  return Q(NonISOCalendarDateToISO(calendar, fields, overflow));
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate */
export function NonISOMonthDayToISOReferenceDate(
  _calendar: CalendarType,
  _fields: CalendarFieldsRecord,
  _overflow: 'constrain' | 'reject',
): never {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate */
export function CalendarMonthDayToISOReferenceDate(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  overflow: 'constrain' | 'reject',
): PlainCompletion<ISODateRecord> {
  if (calendar === 'iso8601') {
    Assert(fields.Month !== undefined && fields.Day !== undefined);
    const referenceISOYear = 1972;
    const year = fields.Year === undefined ? referenceISOYear : fields.Year;
    const result = Q(RegulateISODate(year, fields.Month, fields.Day, overflow));
    return CreateISODateRecord(referenceISOYear, result.Month, result.Day);
  }
  return Q(NonISOMonthDayToISOReferenceDate(calendar, fields, overflow));
}


// NonISOCalendarISOToDate
/** https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendarisotodate */
export function NonISOCalendarISOToDate(
  _calendar: CalendarType,
  _isoDate: ISODateRecord,
): CalendarDateRecord {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate */
export function CalendarISOToDate(
  calendar: CalendarType,
  isoDate: ISODateRecord,
): CalendarDateRecord {
  if (calendar === 'iso8601') {
    const inLeapYear = MathematicalInLeapYear(EpochTimeForYear(isoDate.Year)) === 1;
    return {
      Era: undefined,
      EraYear: undefined,
      Year: isoDate.Year,
      Month: isoDate.Month,
      MonthCode: CreateMonthCode(isoDate.Month, false),
      Day: isoDate.Day,
      DayOfWeek: ISODayOfWeek(isoDate),
      DayOfYear: ISODayOfYear(isoDate),
      WeekOfYear: ISOWeekOfYear(isoDate),
      DaysInWeek: 7,
      DaysInMonth: ISODaysInMonth(isoDate.Year, isoDate.Month),
      DaysInYear: MathematicalDaysInYear(isoDate.Year),
      MonthsInYear: 12,
      InLeapYear: inLeapYear,
    };
  }
  return NonISOCalendarISOToDate(calendar, isoDate);
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields */
export function CalendarExtraFields(
  calendar: CalendarType,
  _fields: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
  if (calendar === 'iso8601') {
    return [];
  }
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisofieldkeystoignore */
export function NonISOFieldKeysToIgnore(
  _calendar: CalendarType,
  _keys: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore */
export function CalendarFieldKeysToIgnore(
  calendar: CalendarType,
  keys: readonly CalendarFieldsRecordEnumerationKey[],
): CalendarFieldsRecordEnumerationKey[] {
  if (calendar === 'iso8601') {
    const ignoredKeys: CalendarFieldsRecordEnumerationKey[] = [];
    for (const key of keys) {
      ignoredKeys.push(key);
      if (key === 'month') {
        ignoredKeys.push('month-code');
      } else if (key === 'month-code') {
        ignoredKeys.push('month');
      }
    }
    return ignoredKeys;
  }
  return NonISOFieldKeysToIgnore(calendar, keys);
}

/** https://tc39.es/proposal-temporal/#sec-temporal-nonisoresolvefields */
export function NonISOResolveFields(
  _calendar: CalendarType,
  _fields: CalendarFieldsRecord,
  _type: 'date' | 'year-month' | 'month-day',
): CalendarFieldsRecord {
  mark_OtherCalendarNotImplemented();
  unreachable_OtherCalendarNotImplemented();
}

/** https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields */
export function* CalendarResolveFields(
  calendar: CalendarType,
  fields: CalendarFieldsRecord,
  type: 'date' | 'year-month' | 'month-day',
): PlainEvaluator<void> {
  if (calendar === 'iso8601') {
    if ((type === 'date' || type === 'year-month') && fields.Year === undefined) {
      return Throw.TypeError('"year" is required');
    }
    if ((type === 'date' || type === 'month-day') && fields.Day === undefined) {
      return Throw.TypeError('"day" is required');
    }
    const month = fields.Month;
    const monthCode = fields.MonthCode;
    if (monthCode === undefined) {
      if (month === undefined) {
        return Throw.TypeError('"month-code" or "month" is required');
      }
    }
    Assert(typeof monthCode === 'string');
    const parsedMonthCode = Q(yield* ParseMonthCode(monthCode));
    if (parsedMonthCode.IsLeapMonth) {
      return Throw.RangeError('Invalid leap month');
    }
    if (parsedMonthCode.MonthNumber > 12) {
      return Throw.RangeError('Invalid month');
    }
    if (month !== undefined && month !== parsedMonthCode.MonthNumber) {
      return Throw.RangeError('Invalid month');
    }
    fields.Month = parsedMonthCode.MonthNumber;
  } else {
    Q(NonISOResolveFields(calendar, fields, type));
  }
}
