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}`
}

export const DateFilterOperators = {
  TODAY: { value: 'today', stringKey: 'viewFilter.today' },
  YESTERDAY: { value: 'yesterday', stringKey: 'viewFilter.yesterday' },
  TOMORROW: { value: 'tomorrow', stringKey: 'viewFilter.tomorrow' },
  ONE_WEEK_AGO: { value: 'one_week_ago', stringKey: 'viewFilter.oneWeekAgo' },
  THIS_WEEK: { value: 'this_week', stringKey: 'viewFilter.thisWeek' },
  NEXT_WEEK: { value: 'next_week', stringKey: 'viewFilter.nextWeek' },
  ONE_MONTH_AGO: {
    value: 'one_month_ago',
    stringKey: 'viewFilter.oneMonthAgo',
  },
  THIS_MONTH: { value: 'this_month', stringKey: 'viewFilter.thisMonth' },
  NEXT_MONTH: { value: 'next_month', stringKey: 'viewFilter.nextMonth' },
  ONE_YEAR_AGO: { value: 'one_year_ago', stringKey: 'viewFilter.oneYearAgo' },
  THIS_YEAR: { value: 'this_year', stringKey: 'viewFilter.thisYear' },
  NEXT_YEAR: { value: 'next_year', stringKey: 'viewFilter.nextYear' },
  NR_DAYS_AGO: {
    value: 'nr_days_ago',
    stringKey: 'viewFilter.nrDaysAgo',
    hasNrInputValue: true,
  },
  NR_DAYS_FROM_NOW: {
    value: 'nr_days_from_now',
    stringKey: 'viewFilter.nrDaysFromNow',
    hasNrInputValue: true,
  },
  NR_WEEKS_AGO: {
    value: 'nr_weeks_ago',
    stringKey: 'viewFilter.nrWeeksAgo',
    hasNrInputValue: true,
  },
  NR_WEEKS_FROM_NOW: {
    value: 'nr_weeks_from_now',
    stringKey: 'viewFilter.nrWeeksFromNow',
    hasNrInputValue: true,
  },
  NR_MONTHS_AGO: {
    value: 'nr_months_ago',
    stringKey: 'viewFilter.nrMonthsAgo',
    hasNrInputValue: true,
  },
  NR_MONTHS_FROM_NOW: {
    value: 'nr_months_from_now',
    stringKey: 'viewFilter.nrMonthsFromNow',
    hasNrInputValue: true,
  },
  NR_YEARS_AGO: {
    value: 'nr_years_ago',
    stringKey: 'viewFilter.nrYearsAgo',
    hasNrInputValue: true,
  },
  NR_YEARS_FROM_NOW: {
    value: 'nr_years_from_now',
    stringKey: 'viewFilter.nrYearsFromNow',
    hasNrInputValue: true,
  },
  EXACT_DATE: {
    value: 'exact_date',
    stringKey: 'viewFilter.exactDate',
    hasDateInputValue: true,
  },
}

const parseFilterValueAsDate = (
  filterValue,
  timezone = null,
  dateFormat = 'YYYY-MM-DD'
) => {
  const filterDate = moment.utc(filterValue, dateFormat, true)
  if (!filterDate.isValid()) {
    throw new Error('Invalid date format')
  } else if (timezone) {
    filterDate.tz(timezone, true)
  }
  return filterDate
}

const parseFilterValueAsNumber = (filterValue) => {
  try {
    return parseInt(filterValue)
  } catch {
    return null
  }
}

// Please be aware that momentjs modifies filterDate in place, so
// make sure to clone it before modifying it.
export const DATE_FILTER_OPERATOR_BOUNDS = {
  [DateFilterOperators.TODAY.value]: (filterDate) => [
    filterDate.startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
  [DateFilterOperators.YESTERDAY.value]: (filterDate) => [
    filterDate.subtract(1, 'days').startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
  [DateFilterOperators.TOMORROW.value]: (filterDate) => [
    filterDate.add(1, 'days').startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
  [DateFilterOperators.ONE_WEEK_AGO.value]: (filterDate) => [
    filterDate.subtract(1, 'weeks').startOf('week').add(1, 'days'), // Start of the week is Sunday, so add 1 day to get Monday.
    filterDate.clone().add(1, 'weeks'),
  ],
  [DateFilterOperators.ONE_MONTH_AGO.value]: (filterDate) => [
    filterDate.subtract(1, 'months').startOf('month'),
    filterDate.clone().add(1, 'months'),
  ],
  [DateFilterOperators.ONE_YEAR_AGO.value]: (filterDate) => [
    filterDate.subtract(1, 'years').startOf('year'),
    filterDate.clone().add(1, 'year'),
  ],
  [DateFilterOperators.THIS_WEEK.value]: (filterDate) => [
    filterDate.startOf('week').add(1, 'days'), // Start of the week is Sunday, so add 1 day to get Monday.
    filterDate.clone().add(1, 'week'),
  ],
  [DateFilterOperators.THIS_MONTH.value]: (filterDate) => [
    filterDate.startOf('month'),
    filterDate.clone().add(1, 'months'),
  ],
  [DateFilterOperators.THIS_YEAR.value]: (filterDate) => [
    filterDate.startOf('year'),
    filterDate.clone().add(1, 'years'),
  ],
  [DateFilterOperators.NEXT_WEEK.value]: (filterDate) => [
    filterDate.add(1, 'weeks').startOf('week').add(1, 'days'), // Start of the week is Sunday, so add 1 day to get Monday.
    filterDate.clone().add(1, 'week'),
  ],
  [DateFilterOperators.NEXT_MONTH.value]: (filterDate) => [
    filterDate.add(1, 'months').startOf('month'),
    filterDate.clone().add(1, 'months'),
  ],
  [DateFilterOperators.NEXT_YEAR.value]: (filterDate) => [
    filterDate.add(1, 'years').startOf('year'),
    filterDate.clone().add(1, 'years'),
  ],
  [DateFilterOperators.NR_DAYS_AGO.value]: (filterDate) => [
    filterDate.startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
  [DateFilterOperators.NR_WEEKS_AGO.value]: (filterDate) => [
    filterDate.startOf('week').add(1, 'days'), // Start of the week is Sunday, so add 1 day to get Monday.
    filterDate.clone().add(1, 'weeks'),
  ],
  [DateFilterOperators.NR_MONTHS_AGO.value]: (filterDate) => [
    filterDate.startOf('month'),
    filterDate.clone().add(1, 'months'),
  ],
  [DateFilterOperators.NR_YEARS_AGO.value]: (filterDate) => [
    filterDate.startOf('year'),
    filterDate.clone().add(1, 'years'),
  ],
  [DateFilterOperators.NR_DAYS_FROM_NOW.value]: (filterDate) => [
    filterDate.startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
  [DateFilterOperators.NR_WEEKS_FROM_NOW.value]: (filterDate) => [
    filterDate.startOf('week').add(1, 'days'), // Start of the week is Sunday, so add 1 day to get Monday.
    filterDate.clone().add(1, 'weeks'),
  ],
  [DateFilterOperators.NR_MONTHS_FROM_NOW.value]: (filterDate) => [
    filterDate.startOf('month'),
    filterDate.clone().add(1, 'months'),
  ],
  [DateFilterOperators.NR_YEARS_FROM_NOW.value]: (filterDate) => [
    filterDate.startOf('year'),
    filterDate.clone().add(1, 'years'),
  ],
  [DateFilterOperators.EXACT_DATE.value]: (filterDate) => [
    filterDate.startOf('day'),
    filterDate.clone().add(1, 'days'),
  ],
}

export const DATE_FILTER_OPERATOR_DELTA_MAP = {
  [DateFilterOperators.EXACT_DATE.value]: (
    filterDate,
    filterValue,
    timezone
  ) => {
    return parseFilterValueAsDate(filterValue, timezone)
  },
  // days
  [DateFilterOperators.NR_DAYS_AGO.value]: (filterDate, filterValue) => {
    return filterDate.subtract(parseFilterValueAsNumber(filterValue), 'days')
  },
  [DateFilterOperators.NR_DAYS_FROM_NOW.value]: (filterDate, filterValue) => {
    return filterDate.add(parseFilterValueAsNumber(filterValue), 'days')
  },
  // weeks
  [DateFilterOperators.NR_WEEKS_AGO.value]: (filterDate, filterValue) => {
    return filterDate.subtract(parseFilterValueAsNumber(filterValue), 'weeks')
  },
  [DateFilterOperators.NR_WEEKS_FROM_NOW.value]: (filterDate, filterValue) => {
    return filterDate.add(parseFilterValueAsNumber(filterValue), 'weeks')
  },
  // months
  [DateFilterOperators.NR_MONTHS_AGO.value]: (filterDate, filterValue) => {
    return filterDate.subtract(parseFilterValueAsNumber(filterValue), 'months')
  },
  [DateFilterOperators.NR_MONTHS_FROM_NOW.value]: (filterDate, filterValue) => {
    return filterDate.add(parseFilterValueAsNumber(filterValue), 'months')
  },
  // years
  [DateFilterOperators.NR_YEARS_AGO.value]: (filterDate, filterValue) => {
    return filterDate.subtract(parseFilterValueAsNumber(filterValue), 'years')
  },
  [DateFilterOperators.NR_YEARS_FROM_NOW.value]: (filterDate, filterValue) => {
    return filterDate.add(parseFilterValueAsNumber(filterValue), 'years')
  },
}