/**
 * @module data/Transport
 */
'use strict';

import { readCookie } from '../utils/cookie';
import EventBus from '../utils/EventBus';
import { getConfig, constants } from '../../utils'; // NOTE: event name is all lower case as per DOM convention

window.addEventListener('unhandledrejection', function unhandledRejectionHandler(e) {
  // NOTE: e.preventDefault() must be manually called to prevent the default
  // action which is currently to log the stack trace to console.warn
  e.preventDefault(); // NOTE: parameters are properties of the event detail property

  var reason = e.reason || (e.detail ? e.detail.reason : null);
  var promise = e.promise || (e.detail ? e.detail.promise : null); // See Promise.onPossiblyUnhandledRejection for parameter documentation

  if (reason) {
    // eslint-disable-next-line
    console.error(reason.stack || reason);
  } else {
    // eslint-disable-next-line
    console.error('unhandled rejection');
  }

  if (promise && promise.catch) {
    promise.catch(function (error) {
      if (error && error.xmlhttp) {
        EventBus.emit('invalidResponse', error);
      }
    });
  }
});
/**
 * @alias data/Store:Transport
 */

var Transport =
/*#__PURE__*/
function () {
  function Transport() {
    this.requests = {};
  }

  var _proto = Transport.prototype;

  _proto.getRequestId = function getRequestId() {
    return null;
  };

  _proto.getRequestIdFieldName = function getRequestIdFieldName() {
    return null;
  };

  _proto.createError = function createError(message, xmlhttp, response) {
    var error = new Error(message);
    error.status = xmlhttp.status;
    error.statusText = xmlhttp.statusText;
    error.responseJSON = response;
    error.xmlhttp = xmlhttp;
    return error;
  }
  /**
   * Low level method that uses XHR to send the request and get response
   * @param {string} requestKey this key will be used to store result of response, it has to be a hash of parameters
   * @param {string} method
   * @param {string} url
   * @param {object} [headers]
   * @param {string} requestBody
   * @param {XMLHttpRequestResponseType} responseType ("" | "arraybuffer" | "blob" | "document" | "json" | "text")
   * @param {boolean} [skipLoader] if true no events will be emitted to EventBus
   * @param {boolean} [responseStatusValidation] JEDI may respond with 200 and an error, setting this to true validates the result
   * @param {boolean} [skipParseResponseJSON] JEDI may respond with valid result which non compatible with JSON format (for example, file upload mechanism)
   * @param {boolean} [compactResponseFormat] whether or not return additional information
   * @return {Promise<object>}
   */
  ;

  _proto.ajaxRequest = function ajaxRequest(_ref) {
    var _this = this;

    var requestKey = _ref.requestKey,
        method = _ref.method,
        url = _ref.url,
        headers = _ref.headers,
        requestBody = _ref.requestBody,
        responseType = _ref.responseType,
        skipLoader = _ref.skipLoader,
        responseStatusValidation = _ref.responseStatusValidation,
        skipParseResponseJSON = _ref.skipParseResponseJSON,
        _ref$compactResponseF = _ref.compactResponseFormat,
        compactResponseFormat = _ref$compactResponseF === void 0 ? true : _ref$compactResponseF;

    //TODO Handle empty request key
    if (this.requests[requestKey]) {
      return this.requests[requestKey];
    }

    if (!skipLoader) {
      EventBus.emit(constants.eventBeforeAjaxRequest);
    }

    return this.requests[requestKey] = new Promise(function (resolve, reject) {
      var xmlhttp = new XMLHttpRequest(); // prevent ie caching, can't stop it via headers for IE

      if (method.toUpperCase() === 'GET') {
        url = Transport.addUrlParameter(url, 'nocache', new Date().getTime());
      }

      var requestId = _this.getRequestId();

      var requestIdFieldName = _this.getRequestIdFieldName();

      if (requestId && requestIdFieldName) {
        url = Transport.addUrlParameter(url, requestIdFieldName, requestId);
      }

      xmlhttp.open(method, url, true);

      if (responseType) {
        // Paying some respect to poor miserable IE
        // responseType can be changed only after XMLHttpRequest is open
        xmlhttp.responseType = responseType;
      }

      xmlhttp.setRequestHeader('CSRFTOKEN', readCookie('CSRFTOKEN'));

      if (getConfig().isDetailedLogsEnabled) {
        xmlhttp.setRequestHeader('detailedRef', top.location.href);
      }

      Object.keys(headers || {}).forEach(function (name) {
        xmlhttp.setRequestHeader(name, headers[name]);
      });

      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState === XMLHttpRequest.DONE) {
          delete _this.requests[requestKey];

          if (!skipLoader) {
            EventBus.emit(constants.eventAfterAjaxRequest);
          }

          var response;

          if (xmlhttp.response !== '') {
            if (!skipParseResponseJSON) {
              try {
                response = JSON.parse(xmlhttp.response);
              } catch (e) {
                //TODO Should it cause an error?
                response = null;
              }
            } else {
              response = xmlhttp.response;
            }
          }

          if ([200, 201, 204].indexOf(xmlhttp.status) > -1) {
            if (responseStatusValidation) {
              if (response && response.status && response.status.code === 10008) {
                // Not logged in or session timeout
                EventBus.emit('sessionExpired');
                return;
              }

              if (response !== null && response.status !== undefined && !response.status.success) {
                reject(_this.createError('JEDI Error', xmlhttp, response));
                return;
              }
            }

            if (compactResponseFormat) {
              resolve(response);
            } else {
              var rawHeaders = xmlhttp.getAllResponseHeaders().trim().split(/[\r\n]+/);
              var headersMap = rawHeaders.reduce(function (result, header) {
                var _header$split = header.split(': '),
                    key = _header$split[0],
                    parts = _header$split.slice(1);

                result[key.toLowerCase()] = parts.join(': ');
                return result;
              }, {});
              resolve({
                data: response,
                headers: headersMap
              });
            }

            return;
          }

          if (xmlhttp.status === 207) {
            // Multi-parts response
            var boundaryPrefix = 'boundary=';
            var boundaryHeader = xmlhttp.getResponseHeader('Content-Type').split(';').find(function (s) {
              return s.trim().toLowerCase().substr(0, boundaryPrefix.length) === boundaryPrefix;
            });
            var boundary = boundaryHeader && boundaryHeader.substr(boundaryPrefix.length);

            if (!boundary) {
              reject(_this.createError('Invalid response: empty boundary', xmlhttp, response));
              return;
            }

            var parts = xmlhttp.responseText.split('--' + boundary).map(function (part) {
              return part.trim();
            }).filter(function (part) {
              return part && part !== '--';
            }).map(function (part) {
              var _part$split = part.split(/\r?\n\r?\n/, 2),
                  headers = _part$split[0],
                  body = _part$split[1];

              if (!/Content-Type:\s*application\/json/i.test(headers)) {
                var error = _this.createError('Invalid Content-Type, "application/json" expected', xmlhttp, response);

                error.part = part;
                return error;
              }

              try {
                return JSON.parse(body);
              } catch (error) {
                return error;
              }
            });
            resolve(parts);
            return;
          }

          reject(_this.createError('Network Error', xmlhttp, response));
        }
      };

      xmlhttp.send(requestBody);
    });
  }
  /**
   * JEDI-specific load method
   * @param cmd
   * @param data
   * @param skipLoader
   * @return {*}
   */
  ;

  _proto.load = function load(cmd, data, skipLoader) {
    if (!cmd) {
      throw new Error('Command is not defined');
    }

    return this.ajaxRequest({
      requestKey: cmd + JSON.stringify(data),
      method: 'POST',
      url: '/mobile/api/proxy.html?cmd=' + cmd,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
      },
      requestBody: Transport.toQueryString(data || {}),
      skipLoader: skipLoader,
      responseStatusValidation: true,
      skipParseResponseJSON: false
    });
  }
  /**
   * JEDI-specific load method
   * @param cmd
   * @param data
   * @param skipLoader
   * @return {*}
   */
  ;

  _proto.loadBlob = function loadBlob(cmd, data, skipLoader) {
    if (!cmd) {
      throw new Error('Command is not defined');
    }

    return this.ajaxRequest({
      requestKey: cmd + JSON.stringify(data),
      method: 'POST',
      url: '/mobile/api/proxy.html?cmd=' + cmd,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
      },
      responseType: 'blob',
      requestBody: Transport.toQueryString(data || {}),
      skipLoader: skipLoader,
      responseStatusValidation: false,
      compactResponseFormat: false,
      skipParseResponseJSON: true
    });
  }
  /**
   * PAS-specific load method
   * @param requestType
   * @param url
   * @param data
   * @param skipLoader
   * @return {*}
   */
  ;

  _proto.loadFromPAS = function loadFromPAS(requestType, url, data, skipLoader) {
    var contentType = 'application/x-www-form-urlencoded; charset=utf-8';

    if (['POST', 'PUT', 'DELETE'].indexOf(requestType) > -1) {
      contentType = 'application/json';
    }

    return this.ajaxRequest({
      requestKey: requestType + ':' + url + JSON.stringify(data),
      method: requestType,
      url: '/mobile/restapi/v1.0' + url,
      headers: {
        Accept: 'application/json',
        'Content-Type': contentType
      },
      requestBody: JSON.stringify(data) || null,
      skipLoader: skipLoader,
      responseStatusValidation: false,
      skipParseResponseJSON: false
    });
  };

  _proto.loadFromPASBatch = function loadFromPASBatch(requestType, url, data, skipLoader) {
    if (!(data instanceof Array) || !data.length) {
      throw new Error('Invalid data, not empty array expected');
    }

    if (data.length === 1) {
      return this.loadFromPAS(requestType, url, data[0], skipLoader).then(function (response) {
        return [response];
      });
    }

    var boundary = 'Boundary_' + Date.now() + '_' + String(Math.random()).substr(2);
    var prefix = '--' + boundary + '\nContent-Type: application/json\n\n';
    var suffix = '\n';
    var end = '--' + boundary + '--';
    var requestBody = prefix + data.map(function (data) {
      return JSON.stringify(data);
    }).join(suffix + prefix) + suffix + end;
    return this.ajaxRequest({
      requestKey: requestType + ':' + url + requestBody,
      method: requestType,
      url: '/mobile/restapi/v1.0' + url,
      headers: {
        'Content-Type': 'multipart/mixed; boundary=' + boundary
      },
      requestBody: requestBody,
      skipLoader: skipLoader,
      responseStatusValidation: false,
      skipParseResponseJSON: true
    }).then(function (parts) {
      // Remove first part with status info
      parts.shift();
      return parts;
    });
  };

  Transport.addUrlParameter = function addUrlParameter(url, parameterName, parameterValue) {
    url += url.indexOf('?') >= 0 ? '&' : '?';
    url += parameterName + '=' + parameterValue;
    return url;
  };

  Transport.toQueryString = function toQueryString(object) {
    var queryString = [];
    Object.keys(object).forEach(function (key) {
      queryString.push(key + '=' + encodeURIComponent(object[key]));
    });
    return queryString.join('&');
  };

  return Transport;
}();

export { Transport as default };