const MOST_ACCURATE_DURATION_FORMAT = 'h:mm:ss.sss'
const MAX_NUMBER_OF_TOKENS_IN_DURATION_FORMAT =
  MOST_ACCURATE_DURATION_FORMAT.split(':').length
export const MAX_BACKEND_DURATION_VALUE_NUMBER_OF_SECS = 86400000000000 // taken from backend timedelta.max.total_seconds()

// Map guarantees the order of the entries
export const DURATION_FORMATS = new Map([
  [
    'h:mm',
    {
      description: 'h:mm (1:23)',
      example: '1:23',
      toString(hours, minutes) {
        return `${hours}:${minutes.toString().padStart(2, '0')}`
      },
      round: (value) => Math.round(value / 60) * 60,
    },
  ],
  [
    'h:mm:ss',
    {
      description: 'h:mm:ss (1:23:40)',
      example: '1:23:40',
      toString(hours, minutes, seconds) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
          .toString()
          .padStart(2, '0')}`
      },
      round: (value) => Math.round(value),
    },
  ],
  [
    'h:mm:ss.s',
    {
      description: 'h:mm:ss.s (1:23:40.0)',
      example: '1:23:40.0',
      toString(hours, minutes, seconds) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
          .toFixed(1)
          .padStart(4, '0')}`
      },
      round: (value) => Math.round(value * 10) / 10,
    },
  ],
  [
    'h:mm:ss.ss',
    {
      description: 'h:mm:ss.ss (1:23:40.00)',
      example: '1:23:40.00',
      toString(hours, minutes, seconds) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
          .toFixed(2)
          .padStart(5, '0')}`
      },
      round: (value) => Math.round(value * 100) / 100,
    },
  ],
  [
    'h:mm:ss.sss',
    {
      description: 'h:mm:ss.sss (1:23:40.000)',
      example: '1:23:40.000',
      toString(hours, minutes, seconds) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
          .toFixed(3)
          .padStart(6, '0')}`
      },
      round: (value) => Math.round(value * 1000) / 1000,
    },
  ],
])

export const DURATION_TOKENS = {
  h: {
    multiplier: 3600,
    parse: (value) => parseInt(value),
  },
  mm: {
    multiplier: 60,
    parse: (value) => parseInt(value),
  },
  ss: {
    multiplier: 1,
    parse: (value) => parseInt(value),
  },
  'ss.s': {
    multiplier: 1,
    parse: (value) => Math.round(parseFloat(value) * 10) / 10,
  },
  'ss.ss': {
    multiplier: 1,
    parse: (value) => Math.round(parseFloat(value) * 100) / 100,
  },
  'ss.sss': {
    multiplier: 1,
    parse: (value) => Math.round(parseFloat(value) * 1000) / 1000,
  },
}

export const roundDurationValueToFormat = (value, format) => {
  if (value === null) {
    return null
  }

  const roundFunc = DURATION_FORMATS.get(format).round
  return roundFunc(value)
}

/**
 * It tries to parse the input value using the given format.
 * If the input value does not match the format, it tries to parse it using
 * the most accurate format if strict is false, otherwise it throws an error.
 */
export const parseDurationValue = (
  inputValue,
  format = MOST_ACCURATE_DURATION_FORMAT,
  strict = false
) => {
  if (inputValue === null || inputValue === undefined || inputValue === '') {
    return null
  }

  // If the input value is a number, we assume it is in seconds.
  if (Number.isFinite(inputValue)) {
    return inputValue > 0 ? inputValue : null
  }

  const parts = inputValue.split(':').reverse()
  let tokens = format.split(':').reverse()
  if (parts.length > tokens.length) {
    if (strict || parts.length > MAX_NUMBER_OF_TOKENS_IN_DURATION_FORMAT) {
      throw new Error(
        `Invalid duration format: ${inputValue} does not match ${format}`
      )
    } else {
      tokens = MOST_ACCURATE_DURATION_FORMAT.split(':').reverse()
    }
  }

  try {
    return tokens.reduce((acc, token, index) => {
      if (index >= parts.length) {
        return acc
      }
      const part = parts[index]
      const parseFunc = DURATION_TOKENS[token].parse
      const number = parseFunc(part)

      if (isNaN(number) || number < 0) {
        throw new Error(
          `Invalid duration format: ${inputValue} does not match ${format}`
        )
      }

      const multiplier = DURATION_TOKENS[token].multiplier
      return acc + number * multiplier
    }, 0)
  } catch (e) {
    return null
  }
}

/**
 * It formats the given duration value using the given format.
 */
export const formatDuration = (value, format) => {
  if (value === null || value === undefined || value === '') {
    return ''
  }

  const hours = Math.floor(value / 3600)
  const mins = Math.floor((value - hours * 3600) / 60)
  const secs = value - hours * 3600 - mins * 60

  const formatFunc = DURATION_FORMATS.get(format).toString
  return formatFunc(hours, mins, secs)
}