"use strict";

var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");

require("core-js/modules/es.array.join");

require("core-js/modules/es.regexp.exec");

require("core-js/modules/es.string.split");

_Object$defineProperty(exports, "__esModule", {
  value: true
});

exports.guessValueType = guessValueType;
exports.coerceValue = coerceValue;

var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with"));

var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));

var _every = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/every"));

var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of"));

var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim"));

var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));

var _parseFloat2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-float"));

var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify"));

var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/typeof"));

var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));

var _isodate = _interopRequireDefault(require("@segment/isodate"));

var _sexagesimal = _interopRequireDefault(require("@mapbox/sexagesimal"));

var _url = _interopRequireDefault(require("url"));

var _lite = _interopRequireDefault(require("mime/lite"));

var valueTypes = _interopRequireWildcard(require("../../constants/value_types"));

// @flow
// Match dates of the form 1999-12-31
var shortDateRegExp = /^(\d{4})-(\d{2})-(\d{2})$/;
var TYPES = {
  boolean: valueTypes.BOOLEAN,
  number: valueTypes.NUMBER,
  undefined: valueTypes.UNDEFINED
};
/**
 * Guess the type of a value:
 * - primitive types (excluding Symbol)
 * - dates
 * - urls (guessing contentType from extension)
 */

function guessValueType(value
/*: Primitive | Array<Primitive>*/
)
/*: $Values<typeof valueTypes>*/
{
  if ((0, _isArray.default)(value)) return valueTypes.ARRAY;
  if (value === null) return valueTypes.NULL;
  if (typeof value !== 'string') return TYPES[(0, _typeof2.default)(value)];

  if (isUrl(value)) {
    // eslint-disable-next-line node/no-deprecated-api
    var parsedUrl = _url.default.parse(value);

    var mimeType = _lite.default.getType(parsedUrl.pathname);

    if (!mimeType) return valueTypes.URL;
    if (mimeType.split('/')[0] === 'image') return valueTypes.IMAGE_URL;
    if (mimeType.split('/')[0] === 'video') return valueTypes.VIDEO_URL;
    if (mimeType.split('/')[0] === 'audio') return valueTypes.AUDIO_URL;
    return valueTypes.URL;
  } // Test date last because this is the most expensive


  if (isShortDate(value)) return valueTypes.DATE;
  if (_isodate.default.is(value)) return valueTypes.DATETIME;
  return valueTypes.STRING;
}
/*:: type NonNullishPrimitive = number | boolean | string*/

/*:: type Value = NonNullishPrimitive | Array<Primitive>*/

/*:: declare function coerceValue(
  value: null,
  type: $Values<typeof valueTypes>
): null*/

/*:: declare function coerceValue(
  value: void,
  type: $Values<typeof valueTypes>
): void*/

/*:: declare function coerceValue(value: Value, type: typeof valueTypes.NULL): null*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.UNDEFINED
): void*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.LOCATION
): [number, number]*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.ARRAY
): Array<Primitive>*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.BOOLEAN
): boolean*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.NUMBER
): number*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.STRING
): string*/

/*:: declare function coerceValue(
  value: Value,
  type: typeof valueTypes.DATE | typeof valueTypes.DATETIME
): Date*/

/*:: declare function coerceValue(
  value: Value,
  type:
    | typeof valueTypes.IMAGE_URL
    | typeof valueTypes.URL
    | typeof valueTypes.VIDEO_URL
    | typeof valueTypes.AUDIO_URL
): string*/


/* eslint-enable no-redeclare */

/**
 * Attempts to coerce a value to `type`, throws if it can't coerce
 */
// eslint-disable-next-line no-redeclare
function coerceValue(value, type) {
  if (value === undefined || value === null) return value;

  switch (type) {
    case valueTypes.UNDEFINED:
      return;

    case valueTypes.NULL:
      return null;

    case valueTypes.LOCATION:
      var parsedLocation = parseLocation(value);
      if (parsedLocation) return parsedLocation;
      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

    case valueTypes.ARRAY:
      if ((0, _isArray.default)(value)) return value; // If string assume either comma-separated or space-separated - choose the
      // one that results in the largest number of values

      if (typeof value === 'string') {
        var spaceSplit = value.split(' ');
        var commaSplit = value.split(',');
        return commaSplit.length >= spaceSplit.length ? commaSplit : spaceSplit;
      }

      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

    case valueTypes.BOOLEAN:
      if (typeof value === 'boolean') return value;
      if (typeof value === 'number') return !!value;

      if (typeof value === 'string') {
        var parsedBool = parseBoolean(value);
        if (parsedBool !== null) return parsedBool;
      }

      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

    case valueTypes.NUMBER:
      if (typeof value === 'number') return value;

      if (typeof value === 'string' && !isNaN((0, _parseFloat2.default)(value))) {
        return (0, _parseFloat2.default)(value);
      }

      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

    case valueTypes.STRING:
      if (typeof value === 'string') return value;
      if (typeof value === 'boolean') return value ? 'yes' : 'no';
      if (typeof value === 'number') return value + '';
      if ((0, _isArray.default)(value)) return (0, _map.default)(value).call(value, function (v) {
        return coerceValue(v, valueTypes.STRING);
      }).join(',');
      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

    case valueTypes.DATE:
      {
        // returns midday (local timezone) dates
        if (typeof value === 'number') return new Date(value);
        if (typeof value !== 'string') throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type); // TODO: Needs to construct date from components, otherwise it will be
        // assumed to be in UTC timezone

        if (isShortDate(value)) return new Date(+new Date(value) + 12 * 60 * 60 * 1000);
        var parsedDateValue = Date.parse(value);
        if (isNaN(parsedDateValue)) throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);
        var date = new Date(parsedDateValue);
        return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12);
      }

    case valueTypes.DATETIME:
      {
        if (typeof value === 'number') return new Date(value);
        if (typeof value !== 'string') throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);

        var _parsedDateValue = Date.parse(value);

        if (isNaN(_parsedDateValue)) throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);
        return new Date(_parsedDateValue);
      }

    case valueTypes.IMAGE_URL:
    case valueTypes.URL:
    case valueTypes.VIDEO_URL:
    case valueTypes.AUDIO_URL:
      if (typeof value === 'string' && isUrl(value)) return value;
      throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);
  }

  throw new Error('Cannot coerce ' + (0, _stringify.default)(value) + ' to ' + type);
}

var TRUE_STRINGS = ['yes', 'true', '1'];
var FALSE_STRINGS = ['no', 'false', '0'];

function parseBoolean(value
/*: string*/
) {
  var _context;

  var v = (0, _trim.default)(_context = value.toLowerCase()).call(_context);
  if ((0, _indexOf.default)(TRUE_STRINGS).call(TRUE_STRINGS, v) > -1) return true;
  if ((0, _indexOf.default)(FALSE_STRINGS).call(FALSE_STRINGS, v) > -1) return false;
  return null;
}
/**
 * Parses location from one of:
 * - a string location record from an ODK form
 * - a sexagesimal pair e.g. `66N 32W`
 * - an array [lon, lat] of either numbers or strings which can be coerced to numbers
 */


function parseLocation(value
/*: any*/
)
/*: [number, number] | null*/
{
  if (typeof value === 'string') {
    var parsed = _sexagesimal.default.pair(value);

    if (parsed) return [parsed[1], parsed[0]];
    var parts = value.split(' '); // ODK location is a string like `-77.23 2.24 10 200` (lon, lat, accuracy, altitude)

    if (parts.length !== 4) return null;

    if ((0, _every.default)(parts).call(parts, isFloat) && withinBounds(+parts[0], +parts[1])) {
      var _context2;

      return (0, _slice.default)(_context2 = (0, _map.default)(parts).call(parts, _parseFloat2.default)).call(_context2, 0, 2);
    }
  } else if ((0, _isArray.default)(value) && value.length === 2 && (0, _every.default)(value).call(value, isFloat) && withinBounds(+value[0], +value[1])) {
    var _context3;

    return (0, _slice.default)(_context3 = (0, _map.default)(value).call(value, _parseFloat2.default)).call(_context3, 0, 2);
  }

  return null;
} // Stricter parsing function, from
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat


function isFloat(value
/*: string | number*/
) {
  if (typeof value === 'number') return true;
  return /^(-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/.test(value);
}

function isShortDate(value
/*: string*/
) {
  return shortDateRegExp.test(value);
} // Check whether a location is within bounds


function withinBounds(lon
/*: number*/
, lat
/*: number*/
) {
  return lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
}

function isUrl(url
/*: string*/
)
/*: boolean*/
{
  return (0, _startsWith.default)(url).call(url, 'https://') || (0, _startsWith.default)(url).call(url, 'http://');
}
//# sourceMappingURL=value_types.js.map