/**
 * @see https://github.com/date-fns/date-fns/pull/348
 * @see https://github.com/date-fns/date-fns/pull/364
 * @see https://github.com/date-fns/date-fns/issues/277
 * @see https://github.com/date-fns/date-fns/issues/284
 * @see https://www.npmjs.com/package/pomeranian-durations
 * @see https://github.com/webpapaya/zeitgeist.js
 * @see https://stackoverflow.com/questions/54103133/parse-time-1hr-30m-4s-as-time-ahead-in-javascript
 *
 * @see ISO_8601
 * @note it accepts `1ssssss`, but not `1ymd` or `1yyyy` (in prod)
 *
 * @example valid
 *    - '1w1d'
 *    - '1w'
 *    - '1m'
 *    - '1d'
 *    - '1s'
 *    - '1m1w1d1h1000s'
 *    - '1M1w1D1h1S'
 *    - '10000'
 *
 * @example invalid
 *    - '1yyy'
 *    - '10y' (_too big_)
 *    - '5y1m1w1d1h1s' (_too big_)
 */
import {
  matchDigitWithNumber,
  match,
  hasLetters,
  hasNumbers,
} from '../shared/utils_generic';
import {
  ErrorKeyKind,
  TtlErrorKeyType,
} from '../shared/constants_domain_specific';
import {
  TimeLetterKind,
  MultiplyByKind,
  VALID_LETTERS,
  MAX_TTL_API_VALUE,
} from './ttl_constants';

/** @perf optimize */
const splitNumberAndLetter = (x: string) =>
  [
    +match(/\d+/, x).join(''),
    match(/[a-z]+/i, x)
      .join('')
      .toUpperCase() as TimeLetterKind,
  ] as [number, string];

export const getNumberLetterPairSeconds = (
  matches: [number, string][]
): number => {
  let inSeconds = 0;

  for (const [digits, letter] of matches) {
    switch (letter) {
      case TimeLetterKind.Second:
        inSeconds += digits * MultiplyByKind.Second;
        break;
      case TimeLetterKind.Hour:
        inSeconds += digits * MultiplyByKind.Hour;
        break;
      case TimeLetterKind.Day:
        inSeconds += digits * MultiplyByKind.Day;
        break;
      case TimeLetterKind.Week:
        inSeconds += digits * MultiplyByKind.Week;
        break;
      case TimeLetterKind.Month:
        inSeconds += digits * MultiplyByKind.Month;
        break;
      case TimeLetterKind.Year:
        inSeconds += digits * MultiplyByKind.Year;
        break;
    }
  }

  return inSeconds;
};

/** @perf could keep TTL always a string */
export const parseTtl = <ValueType extends string | number>(
  valueInput: ValueType
): [number] | [number | undefined, TtlErrorKeyType] => {
  if (typeof valueInput === 'number') {
    return [valueInput];
  } else if (typeof valueInput === 'string' && !hasNumbers(valueInput)) {
    return [, ErrorKeyKind.TtlInvalidFormat];
  }

  const value = valueInput as string;
  const ttl = parseInt(value, 10);

  if (!hasLetters(value) && !Number.isNaN(ttl)) {
    return [ttl];
  } else if (Number.isNaN(ttl) || !matchDigitWithNumber.test(value)) {
    return [, ErrorKeyKind.TtlMissingDefault];
  }

  const matches = match(matchDigitWithNumber, value).map(splitNumberAndLetter);

  if (
    matches.some(
      ([, letter]) => letter.length > 1 || !VALID_LETTERS.includes(letter)
    )
  ) {
    return [ttl, ErrorKeyKind.TtlInvalidFormat];
  }

  const seconds = getNumberLetterPairSeconds(matches);

  if (seconds >= MAX_TTL_API_VALUE) {
    return [seconds, ErrorKeyKind.TtlOverflow];
  } else {
    return [seconds];
  }
};

export const getHasValidTtl = (value: string | number): boolean => {
  const [, error] = parseTtl(value);

  return !error;
};
