/*
 * Copyright 2020 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import {CalendarDate} from './CalendarDate';

/** An interface that is compatible with any object with date fields. */
export interface AnyCalendarDate {
  readonly calendar: Calendar;
  readonly era: string;
  readonly year: number;
  readonly month: number;
  readonly day: number;
  copy(): this;
}

/** An interface that is compatible with any object with time fields. */
export interface AnyTime {
  readonly hour: number;
  readonly minute: number;
  readonly second: number;
  readonly millisecond: number;
  copy(): this;
}

/** An interface that is compatible with any object with both date and time fields. */
export interface AnyDateTime extends AnyCalendarDate, AnyTime {}

export type CalendarIdentifier =
  | 'gregory'
  | 'buddhist'
  | 'chinese'
  | 'coptic'
  | 'dangi'
  | 'ethioaa'
  | 'ethiopic'
  | 'hebrew'
  | 'indian'
  | 'islamic'
  | 'islamic-umalqura'
  | 'islamic-tbla'
  | 'islamic-civil'
  | 'islamic-rgsa'
  | 'iso8601'
  | 'japanese'
  | 'persian'
  | 'roc';

/**
 * The Calendar interface represents a calendar system, including information
 * about how days, months, years, and eras are organized, and methods to perform
 * arithmetic on dates.
 */
export interface Calendar {
  /**
   * A string identifier for the calendar, as defined by Unicode CLDR. See
   * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/supportedValuesOf#supported_calendar_types).
   */
  identifier: CalendarIdentifier;

  /** Creates a CalendarDate in this calendar from the given Julian day number. */
  fromJulianDay(jd: number): CalendarDate;
  /** Converts a date in this calendar to a Julian day number. */
  toJulianDay(date: AnyCalendarDate): number;

  /** Returns the number of days in the month of the given date. */
  getDaysInMonth(date: AnyCalendarDate): number;
  /** Returns the number of months in the year of the given date. */
  getMonthsInYear(date: AnyCalendarDate): number;
  /** Returns the number of years in the era of the given date. */
  getYearsInEra(date: AnyCalendarDate): number;
  /** Returns a list of era identifiers for the calendar. */
  getEras(): string[];

  /**
   * Returns the minimum month number of the given date's year.
   * Normally, this is 1, but in some calendars such as the Japanese,
   * eras may begin in the middle of a year.
   */
  getMinimumMonthInYear?(date: AnyCalendarDate): number;
  /**
   * Returns the minimum day number of the given date's month.
   * Normally, this is 1, but in some calendars such as the Japanese,
   * eras may begin in the middle of a month.
   */
  getMinimumDayInMonth?(date: AnyCalendarDate): number;
  /** Returns the maximum months across all years. */
  getMaximumMonthsInYear(): number;
  /** Returns the maximum days across all months. */
  getMaximumDaysInMonth(): number;
  /**
   * Returns a date that is the first day of the month for the given date.
   * This is used to determine the month that the given date falls in, if
   * the calendar has months that do not align with the standard calendar months
   * (e.g. fiscal calendars).
   */
  getFormattableMonth?(date: AnyCalendarDate): CalendarDate;

  /** Returns whether the given calendar is the same as this calendar. */
  isEqual?(calendar: Calendar): boolean;

  /** @private */
  balanceDate?(date: AnyCalendarDate): void;
  /** @private */
  balanceYearMonth?(date: AnyCalendarDate, previousDate: AnyCalendarDate): void;
  /** @private */
  constrainDate?(date: AnyCalendarDate): void;
  /** @private */
  isInverseEra?(date: AnyCalendarDate): boolean;
}

/** Represents an amount of time in calendar-specific units, for use when performing arithmetic. */
export interface DateDuration {
  /** The number of years to add or subtract. */
  years?: number;
  /** The number of months to add or subtract. */
  months?: number;
  /** The number of weeks to add or subtract. */
  weeks?: number;
  /** The number of days to add or subtract. */
  days?: number;
}

/** Represents an amount of time, for use whe performing arithmetic. */
export interface TimeDuration {
  /** The number of hours to add or subtract. */
  hours?: number;
  /** The number of minutes to add or subtract. */
  minutes?: number;
  /** The number of seconds to add or subtract. */
  seconds?: number;
  /** The number of milliseconds to add or subtract. */
  milliseconds?: number;
}

/**
 * Represents an amount of time with both date and time components, for use when performing
 * arithmetic.
 */
export interface DateTimeDuration extends DateDuration, TimeDuration {}

export interface DateFields {
  era?: string;
  year?: number;
  month?: number;
  day?: number;
}

export interface TimeFields {
  hour?: number;
  minute?: number;
  second?: number;
  millisecond?: number;
}

export type DateField = keyof DateFields;
export type TimeField = keyof TimeFields;

export type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';

export interface CycleOptions {
  /** Whether to round the field value to the nearest interval of the amount. */
  round?: boolean;
}

export interface CycleTimeOptions extends CycleOptions {
  /**
   * Whether to use 12 or 24 hour time. If 12 hour time is chosen, the resulting value
   * will remain in the same day period as the original value (e.g. if the value is AM,
   * the resulting value also be AM).
   *
   * @default 24
   */
  hourCycle?: 12 | 24;
}
