import {IntlConfig, IntlFormatters, Formatters} from '../types'

import {getNamedFormat, filterProps} from '../utils'
import RelativeTimeFormat, {
  IntlRelativeTimeFormatOptions,
} from '@formatjs/intl-relativetimeformat'
import {FormatError, ErrorCode} from 'intl-messageformat'
import {ReactIntlError, ReactIntlErrorCode} from '../error'

const RELATIVE_TIME_FORMAT_OPTIONS: Array<
  keyof IntlRelativeTimeFormatOptions
> = ['numeric', 'style']

function getFormatter(
  {
    locale,
    formats,
    onError,
  }: Pick<IntlConfig, 'locale' | 'formats' | 'onError'>,
  getRelativeTimeFormat: Formatters['getRelativeTimeFormat'],
  options: Parameters<IntlFormatters['formatRelativeTime']>[2] = {}
): RelativeTimeFormat {
  const {format} = options

  const defaults =
    (!!format && getNamedFormat(formats, 'relative', format, onError)) || {}
  const filteredOptions = filterProps(
    options,
    RELATIVE_TIME_FORMAT_OPTIONS,
    defaults as IntlRelativeTimeFormatOptions
  )

  return getRelativeTimeFormat(locale, filteredOptions)
}

export function formatRelativeTime(
  config: Pick<IntlConfig, 'locale' | 'formats' | 'onError'>,
  getRelativeTimeFormat: Formatters['getRelativeTimeFormat'],
  value: Parameters<IntlFormatters['formatRelativeTime']>[0],
  unit?: Parameters<IntlFormatters['formatRelativeTime']>[1],
  options: Parameters<IntlFormatters['formatRelativeTime']>[2] = {}
): string {
  if (!unit) {
    unit = 'second'
  }
  const RelativeTimeFormat = (Intl as any).RelativeTimeFormat
  if (!RelativeTimeFormat) {
    config.onError(
      new FormatError(
        `Intl.RelativeTimeFormat is not available in this environment.
Try polyfilling it using "@formatjs/intl-relativetimeformat"
`,
        ErrorCode.MISSING_INTL_API
      )
    )
  }
  try {
    return getFormatter(config, getRelativeTimeFormat, options).format(
      value,
      unit
    )
  } catch (e) {
    config.onError(
      new ReactIntlError(
        ReactIntlErrorCode.FORMAT_ERROR,
        'Error formatting relative time.',
        e
      )
    )
  }

  return String(value)
}
