"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.timeStringToMs = exports.cleanTimeStringInput = exports.parseTimeString = void 0;

var _milliseconds = _interopRequireDefault(require("milliseconds"));

var _date = require("./date");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _wrapRegExp(re, groups) { _wrapRegExp = function (re, groups) { return new BabelRegExp(re, undefined, groups); }; var _RegExp = _wrapNativeSuper(RegExp); var _super = RegExp.prototype; var _groups = new WeakMap(); function BabelRegExp(re, flags, groups) { var _this = _RegExp.call(this, re, flags); _groups.set(_this, groups || _groups.get(re)); return _this; } _inherits(BabelRegExp, _RegExp); BabelRegExp.prototype.exec = function (str) { var result = _super.exec.call(this, str); if (result) result.groups = buildGroups(result, this); return result; }; BabelRegExp.prototype[Symbol.replace] = function (str, substitution) { if (typeof substitution === "string") { var groups = _groups.get(this); return _super[Symbol.replace].call(this, str, substitution.replace(/\$<([^>]+)>/g, function (_, name) { return "$" + groups[name]; })); } else if (typeof substitution === "function") { var _this = this; return _super[Symbol.replace].call(this, str, function () { var args = []; args.push.apply(args, arguments); if (typeof args[args.length - 1] !== "object") { args.push(buildGroups(args, _this)); } return substitution.apply(this, args); }); } else { return _super[Symbol.replace].call(this, str, substitution); } }; function buildGroups(result, re) { var g = _groups.get(re); return Object.keys(g).reduce(function (groups, name) { groups[name] = result[g[name]]; return groups; }, Object.create(null)); } return _wrapRegExp.apply(this, arguments); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); }

function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

const toNumberInRange = (str, max) => {
  const asNumber = Number(str);
  return asNumber >= 0 && asNumber <= max ? asNumber : null;
};

const validSeparatorRE = /[.: ]+/;
const separatorFormatRE = /^\d+([.: ]+\d|:)/;
const internalCleanRE = /[^0-9:. ap]/g;

const clean = str => str && str.toLowerCase ? str.toLowerCase().replace(internalCleanRE, '') // remove repeated characters
.replace(/( +|p+|a+|:+)/gi, match => match[0]).trim() : '';
/** @typedef {'a' | 'p' | ''} AorP */

/**
 * @param {string} str
 * @returns {AorP}
 */


const parseAmPm = str => {
  const match = /*#__PURE__*/_wrapRegExp(/[0-9](?:(?![ap])[\s\S])*(a|p)/i, {
    aorp: 1
  }).exec(str); // @ts-ignore


  return (match && match.groups && match.groups.aorp || '').toLowerCase();
};
/**
 * Turns a time string number of milliseconds
 *
 * @param {{ hoursStr: string; minutesStr: string; aOrP: AorP }} options
 * @returns {{ hours: number; minutes: number } | null}
 */


const validOrNull = ({
  hoursStr,
  minutesStr,
  aOrP
}) => {
  const hoursAsNumber = toNumberInRange(hoursStr, aOrP ? 12 : 24);
  const result = {
    minutes: 0,
    hours: 0
  };

  if (hoursAsNumber === null) {
    return null;
  } // special handling for 12 am and 12 pm


  if (hoursAsNumber === 12 && aOrP) {
    result.hours = aOrP === 'a' ? 0 : 12;
  } else if (hoursAsNumber === 24) {
    result.hours = 0;
  } else {
    result.hours = hoursAsNumber + (aOrP === 'p' ? 12 : 0);
  }

  if (!minutesStr) {
    return result;
  }

  const minutesAsNumber = toNumberInRange(minutesStr, 59);

  if (minutesAsNumber === null) {
    return null;
  }

  result.minutes = minutesAsNumber;
  return result;
};
/**
 * Parse a possible time string into an object of hours and minutes. Handles
 * weird time quirks like 12am being 0 hours, etc.
 *
 * @param {string} str
 * @param {{ strict?: boolean }} opts
 * @returns {{ hours: number; minutes: number } | null}
 */


const parseTimeString = (str, {
  strict = false
} = {}) => {
  const cleaned = clean(str);
  const aOrP = parseAmPm(cleaned);
  const isSeparatorFormat = separatorFormatRE.test(cleaned);

  if (isSeparatorFormat) {
    const [hoursStr, minutesStr] = cleaned.split(validSeparatorRE);
    const cleanedMinutes = minutesStr.replace(/[^\d]/g, '');
    const cleanedHours = hoursStr.replace(/[^\d]/g, '');

    if (strict) {
      if (!cleanedMinutes || !cleanedHours || cleanedHours.length !== 2) {
        return null;
      }
    }

    if (cleanedMinutes && cleanedMinutes.length !== 2) {
      return null;
    }

    return validOrNull({
      hoursStr,
      minutesStr: cleanedMinutes,
      aOrP
    });
  } else {
    const match = /^(\d{1,4})/.exec(cleaned);
    const leadNumbers = match && match[0];

    if (!leadNumbers) {
      return null;
    }

    if (leadNumbers.length === 1) {
      if (!aOrP || strict) {
        return null;
      }

      return validOrNull({
        hoursStr: leadNumbers,
        minutesStr: '',
        aOrP
      });
    } else if (leadNumbers.length === 2) {
      if (strict) {
        return null;
      }

      return validOrNull({
        hoursStr: leadNumbers,
        minutesStr: '',
        aOrP
      });
    } else if (leadNumbers.length === 3) {
      if (strict) {
        return null;
      }

      return validOrNull({
        hoursStr: leadNumbers.slice(0, 1),
        minutesStr: leadNumbers.slice(1, 3),
        aOrP
      });
    } else if (leadNumbers.length === 4) {
      return validOrNull({
        hoursStr: leadNumbers.slice(0, 2),
        minutesStr: leadNumbers.slice(2, 4),
        aOrP
      });
    }
  }

  return null;
};
/**
 * Meant for real-time cleanup of user input meant to be parsed by
 * `parseTimeString` function above. Importantly it's meant to be typing
 * friendly, meaning it does not have to be parseable as valid at all times.
 * Just enough cleanup to avoid weird characters, and long bad clipboard pastes, etc.
 *
 * @param {string} input
 * @returns {string}
 */


exports.parseTimeString = parseTimeString;

const cleanTimeStringInput = input => {
  if (!input) {
    return '';
  }

  return input.replace(/[^\d. :apm]/gi, '').replace(/(a+|p+| +|m+|\.+|:+)/gi, match => match[0]).slice(0, 10);
};
/**
 * @param {string} str
 * @param {{
 *   timestampFromDay?: number | Date
 *   strict?: boolean
 * }} [opts]
 * @returns
 */


exports.cleanTimeStringInput = cleanTimeStringInput;

const timeStringToMs = (str, {
  timestampFromDay,
  strict = false
} = {}) => {
  const parsed = parseTimeString(str, {
    strict
  });

  if (!parsed) {
    return null;
  }

  return (0, _date.getStartOfDate)(timestampFromDay || Date.now()).valueOf() + _milliseconds.default.hours(parsed.hours) + _milliseconds.default.minutes(parsed.minutes);
};

exports.timeStringToMs = timeStringToMs;