import moment from '@baserow/modules/core/moment'

const dateMapping = {
  EU: {
    momentFormat: 'DD/MM/YYYY',
    humanFormat: 'dd/mm/yyyy',
  },
  US: {
    momentFormat: 'MM/DD/YYYY',
    humanFormat: 'mm/dd/yyyy',
  },
  ISO: {
    momentFormat: 'YYYY-MM-DD',
    humanFormat: 'yyyy-mm-dd',
  },
}

const timeMapping = {
  12: {
    momentFormat: 'hh:mm A',
    humanFormat: 'hh:mm AM',
  },
  24: {
    momentFormat: 'HH:mm',
    humanFormat: 'hh:mm',
  },
}

export const getDateMomentFormat = (type) => {
  if (!Object.prototype.hasOwnProperty.call(dateMapping, type)) {
    throw new Error(`${type} wasn't found in the date mapping.`)
  }
  return dateMapping[type].momentFormat
}

export const getTimeMomentFormat = (type) => {
  if (!Object.prototype.hasOwnProperty.call(timeMapping, type)) {
    throw new Error(`${type} wasn't found in the time mapping.`)
  }
  return timeMapping[type].momentFormat
}

export const getDateHumanReadableFormat = (type) => {
  if (!Object.prototype.hasOwnProperty.call(dateMapping, type)) {
    throw new Error(`${type} wasn't found in the date mapping.`)
  }
  return dateMapping[type].humanFormat
}

export const getTimeHumanReadableFormat = (type) => {
  if (!Object.prototype.hasOwnProperty.call(timeMapping, type)) {
    throw new Error(`${type} wasn't found in the time mapping.`)
  }
  return timeMapping[type].humanFormat
}

/**
 * Returns the timezone for a given field. If the field doesn't have a timezone
 * set, the timezone of the user is returned.
 *
 * @param {Object} field The field object
 * @param {boolean} guess Whether or not to try guess the users timezone
 * @returns {String} The timezone for the field
 * @example
 * getFieldTimezone({ date_include_time: true, date_force_timezone: 'Europe/Amsterdam' }) // => 'Europe/Amsterdam'
 * getFieldTimezone({ date_include_time: false }) // => 'UTC'
 */
export const getFieldTimezone = (field, guess = true) => {
  return field.date_include_time
    ? field.date_force_timezone ||
        (guess && !process.server && moment.tz.guess())
    : null
}

/**
 * Returns the timezone abbreviation for a given field and value.
 * If the value is null or undefined and force=false, an empty string is returned.
 *
 * @param {Object} field The field object
 * @param {String | moment} value The value to parse into a moment object
 * @param {Object} options
 * @param {String} options.format The format to parse the value with
 * @param {Boolean} options.replace Whether to replace the timezone or not
 */
export const getCellTimezoneAbbr = (
  field,
  value,
  { format = 'z', force = false } = {}
) => {
  if (!force && (value === null || value === undefined)) {
    return ''
  }
  const timezone = getFieldTimezone(field)

  return timezone
    ? moment
        .utc(value || undefined)
        .tz(timezone)
        .format(format)
    : 'UTC'
}

export const DATE_FILTER_VALUE_SEPARATOR = '?'

/**
 * Splits the timezone and the filter value from a filter value.
 *
 * @param {*} value  The filter value
 * @param {*} separator  The separator between the timezone and the filter value
 * @returns {Array}  An array with the timezone and the filter value
 */
export const splitTimezoneAndFilterValue = (
  value,
  separator = DATE_FILTER_VALUE_SEPARATOR
) => {
  let timezone = null
  let filterValue

  if (value.includes(separator)) {
    // if the filter value already contains a timezone, use it
    ;[timezone, filterValue] = value.split(separator)
  } else {
    // fallback for values before timezone was added to the filter value
    filterValue = value
  }
  timezone = moment.tz.zone(timezone) ? timezone : null
  return [timezone, filterValue]
}

/**
 * Split the filter value for multi-step date filters.
 * @param {String} value The filter value
 * @param {String} separator The separator between the timezone, the filter value and the operator
 * @returns {Array} An array with the timezone, the filter value and the operator
 */
export const splitMultiStepDateValue = (
  value,
  separator = DATE_FILTER_VALUE_SEPARATOR
) => {
  const splittedValue = value.split(separator)
  if (splittedValue.length === 3) {
    return splittedValue
  } else if (splittedValue.length === 2) {
    // let's assume the timezone has not been provided
    return [null, splittedValue[0], splittedValue[1]]
  } else {
    return [null, '', '']
  }
}

/**
 * Compares an item with a previous item to determine
 * whether a day separator should be rendered.
 *
 * @param {*} items All items that contains the timestamp property
 * @param {String} prop The name of the property that holds the timestamp
 * @param {Number} index Index at which we need to decide if previous and
 *  next item's datetimes differ
 * @returns {Boolean} Whether the timestamps around the index warrant
 *  rendering a date separator
 */
export const shouldDisplayDateSeparator = (items, prop, index) => {
  if (index === items.length - 1) {
    return true
  }
  const tzone = moment.tz.guess()
  const prevDate = moment.utc(items[index][prop]).tz(tzone)
  const currentDate = moment.utc(items[index + 1][prop]).tz(tzone)
  return !prevDate.isSame(currentDate, 'day')
}

/**
 * Formats output for date separators when separating items based on
 * day (today, yesterday, etc.)
 *
 * @param {moment} timestamp The datetime to format
 * @returns {String} The formatted timestamp
 */
export const formatDateSeparator = (timestamp) => {
  return moment.utc(timestamp).tz(moment.tz.guess()).calendar(null, {
    sameDay: '[Today]',
    lastDay: '[Yesterday]',
    lastWeek: 'LL',
    sameElse: 'LL',
  })
}

/**
 * Prepares a value for a multi-step date filter. It combines the timezone,
 * the filter value and the operator into a single string. It puts the operator
 * at the end to keep the compatibility with the old filter values.
 *
 * @param {String} filterValue The filter value
 * @param {String} timezone The timezone
 * @param {String} operator The date filter operator to use
 * @returns {String} The combined value to send to the backend
 */
export const prepareMultiStepDateValue = (filterValue, timezone, operator) => {
  const sep = DATE_FILTER_VALUE_SEPARATOR
  return `${timezone}${sep}${filterValue}${sep}${operator}`
}