"use strict";

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

var _scrapingConnector = require("./scraping-connector");

var errors = _interopRequireWildcard(require("../errors"));

var cj = _interopRequireWildcard(require("cookiejar"));

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; return newObj; } }

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }

function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

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 _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

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 _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }

function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }

function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }

function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }

var ROOT_CATEGORY = 'root';
/**
 * Takes commerce-integrations Integer type and returns the URL
 * representation used to view a product details page on the website.
 *
 * @param {module:@mobify/commerce-integrations/dist/types.Integer} id
 * @returns {String}
 * @private
 */

var getProductUrl = function getProductUrl(id) {
  return "/".concat(id.replace(/\s/g, '-').toLowerCase(), ".html");
};
/**
 * Utility function, takes a string and caplitalizes each word.
 */


var titleCase = function titleCase(str) {
  return str.split(' ').map(function (word) {
    return "".concat(word.charAt(0).toUpperCase()).concat(word.substring(1));
  }).join(' ');
};
/**
 * Takes a Magento product attribute value object and parses out a
 * commerce-integrations VariationPropertyValue type
 *
 * @param {Object} data
 * @returns {module:@mobify/commerce-integrations/dist/types.VariationPropertyValue}
 * @private
 */


var parseVariationPropertyValue = function parseVariationPropertyValue(_ref) {
  var label = _ref.label,
      id = _ref.id;
  return {
    name: label,
    value: id
  };
};
/**
 * Takes a Magento product variation attribute object and parses out a
 * commerce-integrations VariationProperty type
 *
 * @param {Object} data
 * @returns {module:@mobify/commerce-integrations/dist/types.VariationProperty}
 * @private
 */


var parseVariationProperty = function parseVariationProperty(_ref2) {
  var code = _ref2.code,
      label = _ref2.label,
      _ref2$options = _ref2.options,
      options = _ref2$options === void 0 ? [] : _ref2$options;
  return {
    id: code,
    label: label,
    values: options.map(parseVariationPropertyValue)
  };
};
/**
 * Takes a Magento product object and parses out an
 * commerce-integrations Variation type
 *
 * @param {Object} data
 * @returns {module:@mobify/commerce-integrations/dist/types.Variation}
 * @private
 */


var parseVariation = function parseVariation(id, upc, value, config) {
  var convertedValues = Object.keys(value).reduce(function (acc, curr) {
    acc[config.attributes[curr].code] = value[curr];
    return acc;
  }, {}); // Id's in magento look something like this "Eye Of Newt-20 ml-Red-1" which can be broken
  // down to this template "{sku}-{varationPropertyLabel1}-{varationPropertyLabelN}-{NumericalMasterProductId}". The below
  // code makes sure we get these variation property labels to use in the construction of
  // the id value.

  var variationValueLabels = Object.keys(value).map(function (key) {
    return config.attributes[key].options.find(function (option) {
      return option.id === value[key];
    }).label;
  }).sort();
  return {
    id: "".concat([upc].concat(_toConsumableArray(variationValueLabels)).join('-'), "-").concat(config.productId),
    price: parseFloat(config.optionPrices[id].basePrice.amount),
    orderable: true,
    // Magento doesn't show out of stock variations... I think...
    values: convertedValues
  };
};
/**
 * Takes a takes a Magento image object and returns a set of commerce-integrations
 * Image type.
 *
 * @param {Object} data
 * @returns {Array.<module:@mobify/commerce-integrations/dist/types.Image>}
 * @private
 */


var parseImageSet = function parseImageSet(_ref3) {
  var caption = _ref3.caption,
      full = _ref3.full,
      thumb = _ref3.thumb;
  var sets = []; // Parse `full` image

  sets.push({
    images: [{
      title: caption,
      src: full,
      alt: caption
    }],
    variationProperties: [],
    sizeType: 'full'
  }); // Parse `thumb` image

  sets.push({
    images: [{
      title: caption,
      src: thumb,
      alt: caption
    }],
    variationProperties: [],
    sizeType: 'thumb'
  });
  return sets;
};
/**
 * Takes a document object and parses out a commerce-integrations Product type
 *
 * @param {Object} htmlDoc a {@link https://developer.mozilla.org/en-US/docs/Web/API/Document|Document} object
 * @returns {Promise<module:@mobify/commerce-integrations/dist/types.Product>}
 * @private
 */


var _parseProduct = function parseProduct(htmlDoc) {
  var name = htmlDoc.querySelector('.page-title').textContent;
  var description = htmlDoc.querySelector('.product.description').textContent;
  var categoryEl = htmlDoc.querySelector('.breadcrumbs .item:nth-last-child(2) a');
  var priceEl = htmlDoc.querySelector('meta[property="product:price:amount"]');
  var skuEl = htmlDoc.querySelector('.sku .value'); // Parse the categoryId

  var categoryId;

  if (categoryEl) {
    var categoryIdMatch = categoryEl.pathname.match(/\/([^.]+)\.html/);
    categoryId = categoryIdMatch && categoryIdMatch[1];
  } // Parse the currency, price, prices, upc


  var price;
  var upc;

  if (priceEl) {
    price = parseFloat(priceEl.getAttribute('content'));
  }

  if (skuEl) {
    upc = skuEl.textContent;
  } // We'll get alot of the product information from an inline script which contains
  // product details in the form of JSON.


  var productConfig;
  var productScriptEls = htmlDoc.querySelectorAll('.product-add-form script');

  if (productScriptEls && productScriptEls.length > 1) {
    // NOTE: If there are variations there will be more than 1 script
    var productScriptJSON = JSON.parse(productScriptEls[0].textContent);
    productConfig = productScriptJSON['#product_addtocart_form'].configurable.spConfig;
  }

  var galleryConfig;
  var galleryScriptEl = htmlDoc.querySelector('.product.media script');

  if (galleryScriptEl) {
    var galleryScriptJSON = JSON.parse(galleryScriptEl.textContent);
    galleryConfig = galleryScriptJSON['[data-gallery-role=gallery-placeholder]']['mage/gallery/gallery'].data;
  }

  return {
    id: upc,
    name: name,
    imageSets: galleryConfig && galleryConfig.reduce(function (acc, curr) {
      return [].concat(_toConsumableArray(acc), _toConsumableArray(parseImageSet(curr)));
    }, []),
    description: description,
    categoryId: categoryId,
    // NOTE: Without the category in the path, it won't show up in the bread crumbs.
    brand: "Merlin's Potions",
    minimumOrderQuantity: 1,
    stepQuantity: 1,
    upc: upc,
    unit: 'ea',
    price: price,
    prices: productConfig && Object.keys(productConfig.prices).reduce(function (acc, curr) {
      return _objectSpread({}, acc, {}, _defineProperty({}, curr, productConfig.prices[curr].amount));
    }, {}),
    variations: productConfig && Object.keys(productConfig.index).map(function (key) {
      return parseVariation(key, upc, productConfig.index[key], productConfig);
    }),
    variationProperties: productConfig && Object.keys(productConfig.attributes).map(function (attributeId) {
      return parseVariationProperty(productConfig.attributes[attributeId]);
    }),
    variationValues: productConfig && productConfig.defaultValues
  };
}; // Transform Methods


var transformOrderAddress = function transformOrderAddress(shippingAddress) {
  return {
    firstname: shippingAddress.firstName,
    lastname: shippingAddress.lastName,
    telephone: shippingAddress.phone,
    street: [shippingAddress.addressLine1, shippingAddress.addressLine2],
    countryId: shippingAddress.countryCode,
    regionId: shippingAddress.stateCode,
    city: shippingAddress.city,
    postcode: shippingAddress.postalCode
  };
};

var transformShippingMethod = function transformShippingMethod(shippingMethod) {
  return {
    amount: shippingMethod.cost,
    carrier_code: shippingMethod.id
  };
};

var transformCartItem = function transformCartItem(cartItem) {
  return {
    cartItem: {
      sku: cartItem.productId,
      qty: cartItem.quantity
    }
  };
};
/**
 * A connector for www.merlinspotions.com, using screen-scraping.
 *
 * @implements {module:@mobify/commerce-integrations/dist/connectors/interfaces.CommerceConnector}
 */


var MerlinsConnector =
/*#__PURE__*/
function (_ScrapingConnector) {
  _inherits(MerlinsConnector, _ScrapingConnector);

  function MerlinsConnector(_ref4) {
    var _this;

    var window = _ref4.window,
        basePath = _ref4.basePath,
        dondeGeoBasePath = _ref4.dondeGeoBasePath,
        dondeApiBasePath = _ref4.dondeApiBasePath,
        cookieDomainRewrites = _ref4.cookieDomainRewrites;

    _classCallCheck(this, MerlinsConnector);

    _this = _possibleConstructorReturn(this, _getPrototypeOf(MerlinsConnector).call(this, {
      window: window,
      cookieDomainRewrites: cookieDomainRewrites
    }));
    _this.basePath = basePath;
    _this.dondeGeoBasePath = dondeGeoBasePath;
    _this.dondeApiBasePath = dondeApiBasePath;
    return _this;
  }
  /**
   * @private
   */


  _createClass(MerlinsConnector, [{
    key: "loginUrl",
    value: function loginUrl() {
      return "".concat(this.basePath, "/customer/account/login/");
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "login",
    value: function login(username, password) {
      var _this2 = this;

      if (!(username && password)) {
        // This is a magic customer id and shouldn't passed to the server.
        return Promise.resolve({
          id: 'anonymous'
        });
      } // Magento has separate logins for the API and the HTML site. Login to both.


      var apiLogin = this.agent.post("".concat(this.basePath, "/rest/default/V1/integration/customer/token")).send({
        username: username,
        password: password
      }).then(function (res) {
        return _this2.agent.set('Authorization', "Bearer ".concat(res.body));
      })["catch"](function () {
        throw new errors.ServerError('Could not get customer token');
      });
      var htmlLogin = this.agent.get(this.loginUrl()).then(function (res) {
        return _this2.buildDocument(res);
      }).then(function (doc) {
        var titleEl = doc.querySelector('h1.page-title');
        var title = titleEl && titleEl.textContent || undefined;
        var formKeyEl = doc.querySelector('#login-form input[name="form_key"]');
        var formKey = formKeyEl && formKeyEl.value || undefined;

        if (title === 'My Dashboard') {
          // If redirected to the dashboard, we're already logged in.
          return Promise.resolve();
        } else {
          return _this2.agent.post("".concat(_this2.basePath, "/customer/account/loginPost/")).type('form').send({
            form_key: formKey,
            'login[username]': username,
            'login[password]': password,
            persistent_remember_me: 'on',
            send: ''
          });
        }
      });
      return Promise.all([apiLogin, htmlLogin]).then(function () {
        return _this2.getCustomer('me');
      });
    }
    /**
     * Register a new customer account and login.
     *
     * @param {module:@mobify/commerce-integrations/dist/types.CustomerRegistration} data
     * @return {Promise<module:@mobify/commerce-integrations/dist/types.Customer>}
     */

  }, {
    key: "registerCustomer",
    value: function registerCustomer(data) {
      var _this3 = this;

      var defaults = {
        newLetterSignup: false,
        rememberMe: false
      };
      var fields = ['firstName', 'lastName', 'email', 'password'];
      data = Object.assign({}, defaults, data);
      fields.forEach(function (field) {
        if (!data.hasOwnProperty(field)) {
          throw new errors.InvalidArgumentError("Missing required field ".concat(field));
        }
      });
      return this.agent.post("".concat(this.basePath, "/customer/account/createpost/")).type('form').send(_objectSpread({
        success_url: '',
        error_url: '',
        firstname: data.firstName,
        lastname: data.lastName,
        email: data.email,
        password: data.password,
        password_confirmation: data.password
      }, data.rememberMe ? {
        persistent_remember_me: 'on'
      } : {}, {}, data.newLetterSignup ? {
        is_subscribed: 1
      } : {})).then(function (res) {
        return _this3.buildDocument(res);
      }).then(function (doc) {
        // If we see the signup form again, there was an error.
        var form = doc.querySelector('form.form-create-account');
        return form ? Promise.reject(new errors.ServerError('Could not create account')) : data;
      }).then(function () {
        // After creating an account the server leaves the user in
        // a logged-in state. This doesn't cover API login though, so
        // do a part-redundant call to login here.
        return _this3.login(data.email, data.password);
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "logout",
    value: function logout() {
      var _this4 = this;

      return this.agent.post("".concat(this.basePath, "/customer/account/logout/")).then(function () {
        if (!_this4.inBrowser()) {
          // Really kill all cookies, if not in the browser
          _this4.agent.jar = cj.CookieJar();
        }

        var agent = _this4.agent; // Clear out Authorization headers if there are any.

        agent._defaults = agent._defaults.filter(function (def) {
          return !(def.fn === 'set' && def.arguments[0] === 'Authorization');
        });
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getCustomer",
    value: function getCustomer(id) {
      var _this5 = this;

      if (id !== 'me') {
        return Promise.reject(new Error('This backend only supports getting the current customer ' + '(pass "me" as the ID)'));
      }

      return this.agent.get("".concat(this.basePath, "/customer/account/edit/")).then(function (res) {
        return _this5.buildDocument(res);
      }).then(function (doc) {
        // Selector is over-specific to ensure we are on the correct page
        // (could have followed a redirect if logged-out).
        var form = doc.querySelector('#form-validate.form-edit-account');

        if (!form) {
          return Promise.reject(new Error('logged out'));
        } else {
          return Promise.resolve({
            id: 'me',
            firstName: form.querySelector('input[name="firstname"]').value,
            lastName: form.querySelector('input[name="lastname"]').value,
            email: form.querySelector('input[name="email"]').value
          });
        }
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "refreshSession",
    value: function refreshSession() {
      // This is for interface compatibility - it's not necessary
      // on a screen-scraping connector, generally.
      return this.getCustomer('me');
    }
    /**
     * Given a configuration in the form of a plain object, this method returns
     * a new MerlinsConnector instance.
     *
     * @param {Object} config
     *
     * @returns {module:@mobify/commerce-integrations/dist/types.MerlinsConnector} The new MerlinsConnector instance.
     */

  }, {
    key: "getProduct",

    /**
     * @inheritDoc
     */
    value: function getProduct(id) {
      var _this6 = this;

      var url = "".concat(this.basePath).concat(getProductUrl(id));
      return this.agent.get(url).then(function (res) {
        return _this6.buildDocument(res);
      }).then(function (htmlDoc) {
        if (htmlDoc.title === '404 Not Found') {
          throw new errors.NotFoundError('Product Not Found');
        }

        return htmlDoc;
      }).then(function (htmlDoc) {
        return _this6.parseProduct(htmlDoc);
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getProducts",
    value: function getProducts() {
      var _this7 = this;

      var ids = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      var getProductPromises = ids.map(function (id) {
        return _this7.getProduct(id)["catch"](function () {
          return null;
        });
      });
      return Promise.all(getProductPromises).then(function (products) {
        var validProducts = products.filter(function (product) {
          return !!product;
        });
        return {
          count: validProducts.length,
          data: validProducts,
          total: validProducts.length
        };
      });
    }
    /**
     * Takes a OCAPI Product object and parses it into a commerce-integrations Product type.
     * @param {Object} data a OCAPI {@link https://documentation.demandware.com/DOC1/topic/com.demandware.dochelp/OCAPI/18.3/shop/Documents/Product.html|Product} document
     *
     * @returns {Promise<module:@mobify/commerce-integrations/dist/types.Store>}
     */

  }, {
    key: "parseProduct",
    value: function parseProduct(data) {
      return _parseProduct(data);
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "searchProducts",
    value: function searchProducts(productSearchRequest) {
      var _this8 = this;

      var url = "".concat(this.basePath).concat(this.getSearchUrl(productSearchRequest));

      if (!productSearchRequest.count || ![4, 8, 12].includes(productSearchRequest.count)) {
        console.warn("The Merlins backend only supports 'count' values of 4, 8, 12. Defaulting to 4.");
        productSearchRequest.count = 4;
      }

      return this.agent.get(url).then(function (res) {
        return _this8.buildDocument(res);
      }).then(function (htmlDoc) {
        return _this8.parseSearchProducts(htmlDoc, productSearchRequest);
      });
    }
  }, {
    key: "parseGetStore",
    value: function parseGetStore(htmlDoc) {
      // Parse the page into the models unique parts and assign them to the result.
      var mainContent = htmlDoc.getElementById('maincontent');
      var name = mainContent.querySelector('.location-title').textContent;
      var addressLine1 = mainContent.querySelector('[itemprop="streetAddress"]').textContent.trim();
      var city = mainContent.querySelector('[itemprop="addressLocality"]').textContent;
      var country = mainContent.querySelector('.location-crumb.first-crumb').textContent;
      var phone = mainContent.querySelector('[itemprop="telephone"]').textContent.replace(new RegExp('[(]|[)]|[.]|[ ]|[-]|[#]|[x]', 'g'), '');
      var postalCode = mainContent.querySelector('[itemprop="postalCode"]').textContent;
      var hoursContent = this.window.document.createElement('div');
      hoursContent.innerHTML = mainContent.querySelector('#hoursBlock').textContent;
      var hours = {};
      var hoursNodeList = hoursContent.querySelectorAll('.fh-item');
      hoursNodeList.forEach(function (hour) {
        var content = hour.querySelector('[itemprop="name"]');

        if (content) {
          var day = content.textContent.slice(0, -1);
          var openingTime = hour.querySelector('[itemprop="opens"]').textContent;
          var closingTime = hour.querySelector('[itemprop="closes"]').textContent;
          hours[day] = {
            openingTime: openingTime,
            closingTime: closingTime
          };
        }
      });
      return {
        addressLine1: addressLine1,
        city: city,
        country: country,
        name: name,
        phone: phone ? phone : undefined,
        postalCode: postalCode ? postalCode : undefined,
        hours: hours ? hours : undefined
      };
    }
    /**
     * @private
     */

  }, {
    key: "hasAuthorization",
    value: function hasAuthorization() {
      var headers = this.getDefaultHeaders() || {};
      var keys = Object.keys(headers);
      return keys.filter(function (key) {
        return headers[key].Authorization;
      }).length > 0;
    }
    /**
     * Parse an order address
     */

  }, {
    key: "parseOrderAddress",
    value: function parseOrderAddress(data) {
      return {
        firstName: data.firstname,
        lastName: data.lastname,
        phone: data.telephone,
        addressLine1: data.street[0],
        addressLine2: data.street[1],
        city: data.city,
        countryCode: data.country_id,
        stateCode: data.region_id.toString(),
        postalCode: data.postcode
      };
    }
    /**
     * Parse a cart item
     * cart item will not have price until a payment is set.
     */

  }, {
    key: "parseCartItem",
    value: function parseCartItem(data) {
      return {
        id: data.item_id.toString(),
        baseItemPrice: data.price,
        baseLinePrice: data.price * data.qty,
        productId: data.sku,
        productName: data.name,
        quantity: data.qty,
        itemPrice: data.price,
        linePrice: data.price * data.qty
      };
    }
    /**
     * Parse a shipping method
     */

  }, {
    key: "parseShippingMethod",
    value: function parseShippingMethod(data) {
      return {
        id: data.carrier_code,
        cost: data.amount,
        label: "".concat(data.method_title, " - ").concat(data.carrier_title)
      };
    }
    /**
     * Parse a payment method
     */

  }, {
    key: "parsePaymentMethod",
    value: function parsePaymentMethod(data) {
      return {
        id: data.code,
        name: data.title
      };
    }
    /**
     * Parse a coupon item
     */

  }, {
    key: "parseCouponEntry",
    value: function parseCouponEntry(data) {
      return {
        id: data.coupon_code,
        code: data.coupon_code
      };
    }
    /**
     * Parse a cart
     */

    /* eslint-disable camelcase */

  }, {
    key: "parseCart",
    value: function parseCart(data) {
      var billing_address = data.billing_address,
          customer = data.customer;
      var hasBillingAddress = billing_address && billing_address.street[0] !== '';
      return {
        id: data.id,
        billingAddress: hasBillingAddress ? this.parseOrderAddress(billing_address) : undefined,
        customerInfo: {
          id: customer.id ? customer.id.toString() : 'anonymous',
          email: customer.email
        }
      };
    }
    /* eslint-enable camelcase */

    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "createCart",
    value: function createCart(oldCart) {
      var _this9 = this;

      var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      return this.agent.post("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : 'guest-carts')).then(function (res) {
        return res.body;
      }).then(function (cartId) {
        return _this9.getCart(cartId);
      }).then(function (cart) {
        if (oldCart && !cart.customerInfo.email && oldCart.customerInfo.email) {
          cart.customerInfo.email = oldCart.customerInfo.email;
        } // Have not found a way to retrieve shippingAddress from the backend. so lets persist it from the incoming cart parameter to the returned cart.


        if (oldCart && oldCart.shippingAddress) {
          cart.shippingAddress = oldCart.shippingAddress;
        } // Have not found a way to retrieve selectedShippingMethodId from the backend. so lets persist it from the incoming cart parameter to the returned cart.


        if (oldCart && oldCart.selectedShippingMethodId) {
          cart.selectedShippingMethodId = oldCart.selectedShippingMethodId;
        }

        return cart;
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getCart",
    value: function getCart(cartId) {
      var _this10 = this;

      if (!cartId) {
        throw new errors.InvalidArgumentError("Parameter 'cartId' is required");
      }

      var basePath = "".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? "carts/mine" : "guest-carts/".concat(cartId));
      var cartPromise = this.agent.get("".concat(basePath)).then(function (_ref5) {
        var body = _ref5.body;
        return _this10.parseCart(_objectSpread({}, body, {
          id: cartId
        }));
      });
      var itemsPromise = this.agent.get("".concat(basePath, "/items")).then(function (_ref6) {
        var body = _ref6.body;
        return body && body.map(_this10.parseCartItem);
      })["catch"](function () {
        return [];
      });
      var paymentsPromise = this.agent.get("".concat(basePath, "/selected-payment-method")).then(function (_ref7) {
        var body = _ref7.body;
        return body.method ? [{
          id: 'mine',
          methodId: body.method
        }] : [];
      })["catch"](function () {
        return [];
      });
      var totalsPromise = this.agent.get("".concat(basePath, "/totals")).then(function (_ref8) {
        var body = _ref8.body;
        return _this10.parseCartTotals(body);
      })["catch"](function () {
        throw new errors.ServerError("Could not get cart totals");
      });
      return Promise.all([cartPromise, itemsPromise, paymentsPromise, totalsPromise]).then(function (_ref9) {
        var _ref10 = _slicedToArray(_ref9, 4),
            cart = _ref10[0],
            items = _ref10[1],
            payments = _ref10[2],
            totals = _ref10[3];

        // Apply payments, items, totals and payment amount to the cart.
        cart.payments = payments;
        cart.items = items;

        if (cart.payments && cart.payments.length > 0) {
          cart.payments[0].amount = totals.total;
        }

        return _objectSpread({}, cart, {}, totals);
      })["catch"](function () {
        throw new errors.ServerError("Could not retrieve cart");
      });
    }
    /**
     * Parse the cart money totals
     * totals not calculated until shipping address/method is set.
     */

  }, {
    key: "parseCartTotals",
    value: function parseCartTotals(data) {
      return {
        couponEntries: data.coupon_code ? [this.parseCouponEntry(data)] : [],
        subtotal: data.subtotal,
        shipping: data.shipping_amount,
        discounts: data.discount_amount,
        tax: data.tax_amount,
        total: data.base_grand_total
      };
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "deleteCart",
    value: function deleteCart() {
      throw new errors.UnsupportedError('This function is not supported by the Magento API');
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "addCartItem",
    value: function addCartItem(cart, cartItem) {
      var _this11 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!cartItem) {
        throw new errors.InvalidArgumentError("Parameter 'cartItem' is required");
      } // eslint-disable-next-line no-undef


      cartItem = transformCartItem(cartItem);
      cartItem.cartItem.quote_id = cart.id;
      return this.agent.post("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/items")).send(cartItem) // Have not found a way to retrieve shippingAddress and selectedShippingMethodId from the backend and email is not saved in the backend for a guest user.
      // So lets persist these fields on the incoming cart parameter to the returned cart.
      .then(function () {
        return _this11.getCart(cart.id);
      }).then(function (updatedCart) {
        return _objectSpread({}, updatedCart, {
          shippingAddress: cart.shippingAddress,
          customerInfo: cart.customerInfo,
          selectedShippingMethodId: cart.selectedShippingMethodId
        });
      })["catch"](function () {
        throw new errors.ServerError('Could Not Add Cart Item');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "updateCartItem",
    value: function updateCartItem(cart, cartItem) {
      var _this12 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!cartItem) {
        throw new errors.InvalidArgumentError("Parameter 'cartItem' is required");
      }

      var cartItemId = cartItem.id; // eslint-disable-next-line no-undef

      cartItem = transformCartItem(cartItem);
      cartItem.cartItem.quote_id = cart.id;
      return this.agent.put("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/items/").concat(cartItemId)).send(cartItem) // Have not found a way to retrieve shippingAddress from the backend and email is not saved in the backend for a guest user.
      // So lets persist these fields on the incoming cart parameter to the returned cart.
      .then(function () {
        return _this12.getCart(cart.id);
      }).then(function (updatedCart) {
        return _objectSpread({}, updatedCart, {
          shippingAddress: cart.shippingAddress,
          customerInfo: cart.customerInfo
        });
      })["catch"](function () {
        throw new errors.ServerError('Could Not Update Cart Item');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "removeCartItem",
    value: function removeCartItem(cart, cartItemId) {
      var _this13 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!cartItemId) {
        throw new errors.InvalidArgumentError("Parameter 'cartItemId' is required");
      }

      return this.agent["delete"]("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/items/").concat(cartItemId)) // Have not found a way to retrieve shippingAddress and selectedShippingMethodId from the backend and email is not saved in the backend for a guest user.
      // So lets persist these fields on the incoming cart parameter to the returned cart.
      .then(function () {
        return _this13.getCart(cart.id);
      }).then(function (updatedCart) {
        return _objectSpread({}, updatedCart, {
          shippingAddress: cart.shippingAddress,
          customerInfo: cart.customerInfo,
          selectedShippingMethodId: cart.selectedShippingMethodId
        });
      })["catch"](function () {
        throw new errors.ServerError('Could Not Add Cart Item');
      });
    }
    /**
     * This updates the given cart object with the given customerInformation object and returns it.
     * It will not persist in the backend until a payment is added.
     */

  }, {
    key: "setCustomerInformation",
    value: function setCustomerInformation(cart, customerInformation) {
      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!customerInformation) {
        throw new errors.InvalidArgumentError("Parameter 'customerInformation' is required");
      }

      var cartCopy = JSON.parse(JSON.stringify(cart));
      cartCopy.customerInfo.email = customerInformation.email;
      return Promise.resolve(cartCopy);
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "setBillingAddress",
    value: function setBillingAddress(cart, billingAddress) {
      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!billingAddress) {
        throw new errors.InvalidArgumentError("Parameter 'billingAddress' is required");
      }

      var originalBillingAddress = _objectSpread({}, billingAddress, {
        titleCode: undefined // Transform Mobify schema into OCAPI schema
        // eslint-disable-next-line no-undef

      });

      billingAddress = transformOrderAddress(billingAddress);
      return this.agent.post("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/billing-address")).send({
        address: billingAddress
      }).then(function () {
        return _objectSpread({}, cart, {
          billingAddress: originalBillingAddress
        });
      })["catch"](function () {
        throw new errors.ServerError('Could Not Set Billing Address');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getShippingMethods",
    value: function getShippingMethods(cart) {
      var _this14 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!cart.shippingAddress) {
        throw new errors.InvalidArgumentError("Parameter 'cart.shippingAddress' is required");
      }

      var hasItems = cart.items && cart.items.length;

      if (!hasItems) {
        console.warn("You must first add items to your cart to get valid shipping methods");
      }

      var shippingAddress = transformOrderAddress(cart.shippingAddress);
      return this.agent.post("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/estimate-shipping-methods")).send({
        address: shippingAddress
      }).then(function (_ref11) {
        var _ref11$body = _ref11.body,
            body = _ref11$body === void 0 ? [] : _ref11$body;
        return body.map(_this14.parseShippingMethod);
      })["catch"](function () {
        throw new errors.ServerError('Could Not Get Shipping Methods');
      });
    }
    /**
     * @private
     */

  }, {
    key: "setShippingInformation",
    value: function setShippingInformation(cart, shippingAddress, shippingMethod) {
      var _this15 = this;

      // Set the shipping method id on the cart. We do this because we can't get
      // shipping information back from the server. If we figure out how to do this
      // we can remove this line.
      cart.selectedShippingMethodId = shippingMethod.id; // Transform the shipping address and method into objects that the server
      // will understand.

      shippingAddress = transformOrderAddress(shippingAddress);
      shippingMethod = transformShippingMethod(shippingMethod); // Append the customer email to the shipping address if we have one.

      if (cart.customerInfo && cart.customerInfo.email) {
        shippingAddress.email = cart.customerInfo.email;
      }

      return this.agent.post("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/shipping-information")).send({
        addressInformation: {
          shippingMethodCode: shippingMethod.carrier_code,
          shippingCarrierCode: shippingMethod.carrier_code,
          shipping_address: shippingAddress
        }
      }).then(function (_ref12) {
        var body = _ref12.body;

        var totals = _this15.parseCartTotals(body.totals);

        if (cart.payments && cart.payments.length > 0) {
          cart.payments[0].amount = totals.total;
        }

        return _objectSpread({}, cart, {}, totals);
      })["catch"](function () {
        throw new errors.ServerError('Could Not Set Shipping Information');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "setShippingMethod",
    value: function setShippingMethod(cart, shippingMethod) {
      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!shippingMethod) {
        throw new errors.InvalidArgumentError("Parameter 'shippingMethod' is required");
      }

      if (!cart.shippingAddress) {
        throw new errors.InvalidArgumentError("Shipping Address must be set prior to setting shipping method");
      }

      return this.setShippingInformation(cart, cart.shippingAddress, shippingMethod);
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "setShippingAddress",
    value: function setShippingAddress(cart, shippingAddress) {
      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!shippingAddress) {
        throw new errors.InvalidArgumentError("Parameter 'shippingAddress' is required");
      }

      shippingAddress.titleCode = undefined;
      var shippingMethods = cart.shippingMethods,
          selectedShippingMethod = cart.selectedShippingMethod; // If we don't have the shipping methods set, fake it, and return the cart with the
      // shipping address merged in.

      if (!shippingMethods) {
        return Promise.resolve(_objectSpread({}, cart, {
          shippingAddress: shippingAddress
        }));
      }

      return this.setShippingInformation(cart, shippingAddress, selectedShippingMethod || shippingMethods[0]);
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getPaymentMethods",
    value: function getPaymentMethods(cart) {
      var _this16 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      return this.agent.get("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/payment-methods")).then(function (_ref13) {
        var _ref13$body = _ref13.body,
            body = _ref13$body === void 0 ? [] : _ref13$body;
        return body.map(_this16.parsePaymentMethod);
      })["catch"](function () {
        throw new errors.ServerError('Could not get payment methods');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "setPayment",
    value: function setPayment(cart, payment) {
      var _this17 = this;

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!payment) {
        throw new errors.InvalidArgumentError("Parameter 'payment' is required");
      }

      if (!payment.methodId) {
        throw new errors.InvalidArgumentError("Payment methodId address on parameter 'payment' is required");
      }

      if (!cart.customerInfo.email) {
        throw new errors.InvalidArgumentError("Email on cart is required");
      }

      if (!cart.billingAddress) {
        throw new errors.InvalidArgumentError("Billing address on cart is required");
      }

      if (!cart.shippingAddress) {
        throw new errors.InvalidArgumentError("Shipping address on cart is required");
      }

      var billingAddress = transformOrderAddress(cart.billingAddress);
      var paymentInformation = {
        paymentMethod: {
          method: payment.methodId
        },
        billingAddress: billingAddress,
        email: cart.customerInfo.email
      };
      var basePath = "".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id));
      return this.agent.post("".concat(basePath, "/set-payment-information")).send(paymentInformation) // Have not found a way to retrieve shippingAddress and selectedShippingMethodId from the backend and email is not saved in the backend for a guest user.
      // So lets persist these fields on the incoming cart parameter to the returned cart.
      .then(function () {
        return _this17.getCart(cart.id);
      }).then(function (updatedCart) {
        return _objectSpread({}, updatedCart, {
          shippingAddress: cart.shippingAddress,
          customerInfo: cart.customerInfo,
          selectedShippingMethodId: cart.selectedShippingMethodId
        });
      })["catch"](function () {
        throw new errors.ServerError('Could not set payment');
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getStore",
    value: function getStore(id) {
      var _this18 = this;

      var storesBasePath = this.basePath.replace('www', 'locations');
      var url = "".concat(storesBasePath, "/").concat(id, ".html?utm_source=storelocator");
      return this.agent.get(url).then(function (res) {
        return _this18.buildDocument(res);
      }).then(function (htmlDoc) {
        return _this18.parseGetStore(htmlDoc);
      }).then(function (data) {
        return _objectSpread({}, data, {
          id: id
        });
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getCategory",
    value: function getCategory(id) {
      var _this19 = this;

      // Always request the basePath for getting all categories
      return this.agent.get(this.basePath).then(function (res) {
        return _this19.buildDocument(res);
      }).then(function (htmlDoc) {
        var category;

        if (id === ROOT_CATEGORY) {
          var categoryEls = _toConsumableArray(htmlDoc.querySelectorAll('.navigation a')).map(function (el) {
            return el.parentElement;
          });

          var categories = categoryEls.map(_this19.parseCategory);
          category = {
            id: ROOT_CATEGORY,
            name: 'Categories',
            categories: categories
          };
        } else {
          var categoryAnchorEl = htmlDoc.querySelector(".navigation a[href*=\"".concat(id, "\"]"));

          if (!categoryAnchorEl) {
            throw new Error('Category Not Found');
          }

          category = _this19.parseCategory(categoryAnchorEl.parentElement);
        }

        return category;
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "getCategories",
    value: function getCategories() {
      var _this20 = this;

      var ids = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      var getCategoryPromises = ids.map(function (id) {
        return _this20.getCategory(id)["catch"](function () {
          return null;
        });
      });
      return Promise.all(getCategoryPromises).then(function (categories) {
        var validCategories = categories.filter(function (category) {
          return !!category;
        });
        return {
          count: validCategories.length,
          data: validCategories,
          total: validCategories.length
        };
      });
    }
    /**
     * Takes a html element that represents a category and parses it into a commerce-integrations
     * Category type.
     *
     * @param {Object} htmlDoc a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     *
     * @returns {Promise<module:@mobify/commerce-integrations/dist/types.Category>}
     */

  }, {
    key: "parseCategory",
    value: function parseCategory(categoryEl) {
      var anchorEl = categoryEl.querySelector('a');
      var idMatch = anchorEl.pathname.match(/[^\\](\w+).html$/);
      return {
        id: idMatch && idMatch[1],
        name: anchorEl.textContent
      };
    }
    /**
     * Takes commerce-integrations ProductSearchRequest type and returns the URL
     * representation used to query the website.
     *
     * @param {module:@mobify/commerce-integrations/dist/types.ProductSearchRequest} productSearchRequest
     *
     * @returns {String}
     */

  }, {
    key: "getSearchUrl",
    value: function getSearchUrl(_ref14) {
      var count = _ref14.count,
          sort = _ref14.sort,
          start = _ref14.start,
          total = _ref14.total,
          query = _ref14.query,
          _ref14$filters = _ref14.filters,
          filters = _ref14$filters === void 0 ? {} : _ref14$filters;
      var params = [];
      var pathname = query ? '/catalogsearch/result/' : "/".concat(filters.categoryId, ".html");

      if (count) {
        params.push("product_list_limit=".concat(count));
      }

      if (total) {
        params.push("product_list_total=".concat(total));
      }

      if (sort) {
        params.push("product_list_order=".concat(sort));
      }

      if (start) {
        params.push("p=".concat(Math.ceil(total / start)));
      }

      if (query) {
        params.push("q=".concat(query));
      } // Compose the filters part of the url


      Object.keys(filters).filter(function (key) {
        return !['categoryId'].includes(key);
      }).forEach(function (key) {
        params.push("".concat(key, "=").concat(filters[key]));
      });
      return "".concat(pathname, "?").concat(params.sort().join('&'));
    }
    /**
     * Takes a dom element representing a merlins search result product tile and parses out an
     * commerce-integrations ProductSearchResult type.
     *
     * @param {Object} productEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     *
     * @returns {module:@mobify/commerce-integrations/dist/types.ProductSearchResult}
     */

  }, {
    key: "parseProductSearchResult",
    value: function parseProductSearchResult(productEl) {
      var linkEl = productEl.querySelector('.product-item-link');
      var imageEl = productEl.querySelector('.product-image-photo');
      var priceEl = productEl.querySelector('.price');
      var stockEl = productEl.querySelector('.stock');
      var productName = linkEl.textContent;
      var productIdMatch = linkEl.href.match(/([^/]+).html$/);
      var productId = productIdMatch ? titleCase(productIdMatch[1].replace(/-/g, ' ')) : undefined;
      var available = !stockEl;
      var price;

      if (available) {
        var priceMatch = priceEl.textContent.match(/\$(.+)/);
        price = priceMatch ? parseFloat(priceEl.textContent.match(/\$(.+)/)[1]) : '';
      } else {
        price = -1; // Something obviously invalid
      }

      var image = {
        alt: imageEl.getAttribute('alt'),
        description: imageEl.getAttribute('alt'),
        src: imageEl.getAttribute('src'),
        title: imageEl.getAttribute('alt')
      };
      return {
        available: available,
        productId: productId,
        productName: productName,
        defaultImage: image,
        price: price,
        variationProperties: []
      };
    }
    /**
     * Takes a dom element representing a merlins sorting select and parses out an
     * array od commerce-integrations SortOption type.
     *
     * @param {Object} sortEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     *
     * @returns {Array.<module:@mobify/commerce-integrations/dist/types.SortOption>}
     */

  }, {
    key: "parseSortingOptions",
    value: function parseSortingOptions(sortEl) {
      var optionEls = sortEl.querySelectorAll('option');
      return _toConsumableArray(optionEls).map(function (el) {
        return {
          id: el.value,
          label: el.innerHTML
        };
      });
    }
    /**
     * Takes a dom element representing a merlins filter and parses out an
     * commerce-integrations Filter type.
     *
     * @param {Object} optionEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     *
     * @returns {module:@mobify/commerce-integrations/dist/types.Filter}
     */

  }, {
    key: "parseFilter",
    value: function parseFilter(optionEl) {
      var label = optionEl.querySelector('.filter-options-title').textContent;
      var propertyId = label.toLowerCase();
      var valueEls = optionEl.querySelectorAll('.items .item');

      var values = _toConsumableArray(valueEls).map(function (valueEl) {
        var countEl = valueEl.querySelector('.count');
        var anchorEl = valueEl.querySelector('a'); // Parse out the count and then remove the element so we can get the label text.

        var count = parseInt(countEl.textContent);
        countEl.remove();
        return {
          count: count,
          label: anchorEl.textContent.trim(),
          value: anchorEl.search.match(/\w=([^&]+)/)[1]
        };
      });

      return {
        propertyId: propertyId,
        label: label,
        values: values
      };
    }
    /**
     * Takes a dom element representing a merlins selected filter and parses out a
     * commerce-integrations Filter type.
     *
     * @param {Object} selectedEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     * @param {String} currentUrl The current page url used to help determine filter values.
     *
     * @returns {module:@mobify/commerce-integrations/dist/types.Filter}
     */

  }, {
    key: "parseSelectedFilter",
    value: function parseSelectedFilter(selectedEl, currentUrl) {
      // Use an anchor to parse out the search string
      var anchorEl = this.window.document.createElement('a');
      anchorEl.href = currentUrl;
      var label = selectedEl.querySelector('.filter-label').textContent;
      var propertyId = label.toLowerCase();
      var valueLabel = selectedEl.querySelector('.filter-value').textContent;
      var valueValue = anchorEl.search.match(new RegExp("".concat(propertyId, "=([^&]+)")))[1];
      return {
        propertyId: propertyId,
        label: label,
        values: [{
          label: valueLabel,
          value: valueValue
        }]
      };
    }
    /**
     * Takes a dom element and parses out an array of commerce-integrations
     * Filter type representing those filters that are selected.
     *
     * @param {Object} selectedFiltersEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     * @param {String} currentUrl The current page url used to help determine filter values.
     *
     * @returns {Array.<module:@mobify/commerce-integrations/dist/types.Filter>}
     */

  }, {
    key: "parseSelectedFilters",
    value: function parseSelectedFilters(selectedFiltersEl, currentUrl) {
      var _this21 = this;

      var selectedEls = selectedFiltersEl.querySelectorAll('.items .item');
      return _toConsumableArray(selectedEls).map(function (el) {
        return _this21.parseSelectedFilter(el, currentUrl);
      });
    }
    /**
     * Takes a dom element and parses out an array of commerce-integrations
     * Filter type.
     *
     * @param {Object} filterOptionsEl a {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Element} object
     * @param {String} currentUrl The current page url used to help determine filter values.
     *
     * @returns {Array.<module:@mobify/commerce-integrations/dist/types.Filter>}
     */

  }, {
    key: "parseFilters",
    value: function parseFilters(filterOptionsEl, currentUrl) {
      var _this22 = this;

      // Note: Merlin's Potion's is unique in the way that it handles filters, normally
      // you'd see checkboxes of all the filters with those that are selected being filled
      // in. That makes it easy to parse. But in this case they are split up in two sections,
      // one is the filters that aren't selected and a list of links, and the other is a
      // section of selected filters with it's value.
      // we'll have to parse these both to get as close to a complete list of all the filters.
      var optionsEls = filterOptionsEl.querySelectorAll('.filter-options-item');
      return _toConsumableArray(optionsEls).map(function (el) {
        return _this22.parseFilter(el, currentUrl);
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "parseSearchProducts",
    value: function parseSearchProducts(htmlDoc, searchRequest) {
      var filters = searchRequest.filters,
          query = searchRequest.query; // Parse get the mainContent element and any product elements in it.

      var mainContent = htmlDoc.getElementById('maincontent');
      var resultEls = mainContent.querySelectorAll('.products.product-items > .product-item');
      var is404 = htmlDoc.title === '404 Not Found';
      var isEmpty = !resultEls.length;

      if (is404 || isEmpty) {
        return {
          count: 0,
          filters: [],
          results: [],
          query: query,
          selectedFilters: filters,
          sortingOptions: [],
          start: 0,
          total: 0
        };
      } // This is the current url of the page, it'll be used to get filter values, etc.


      var currentUrl = JSON.parse(htmlDoc.querySelector('.toolbar-products').getAttribute('data-mage-init')).productListToolbarForm.url; // Parse other page information from document.

      var pageSize = parseInt(mainContent.querySelector('#limiter').value);
      var pageEl = mainContent.querySelector('.pages .current span:last-child');
      var page = pageEl ? parseInt(pageEl.textContent) - 1 : 0;
      var filterOptionsEl = mainContent.querySelector('.filter-options');
      var selectedFiltersEl = mainContent.querySelector('.filter-current');
      var filterOptions = filterOptionsEl ? this.parseFilters(filterOptionsEl, currentUrl) : [];
      var selectedFilters = selectedFiltersEl ? this.parseSelectedFilters(selectedFiltersEl, currentUrl) : [];
      var sortEl = mainContent.querySelector('#sorter');
      var totalEl = mainContent.querySelector('#toolbar-amount .toolbar-number');
      return {
        count: resultEls.length,
        query: htmlDoc.querySelector('#search').value || query,
        filters: [].concat(_toConsumableArray(filterOptions), _toConsumableArray(selectedFilters)),
        results: _toConsumableArray(resultEls).map(this.parseProductSearchResult),
        selectedFilters: selectedFilters.reduce(function (obj, curr) {
          obj[curr.propertyId] = curr.values[0].value;
          return obj;
        }, {}),
        selectedSortingOption: sortEl ? sortEl.value : '',
        sortingOptions: sortEl ? this.parseSortingOptions(sortEl) : [],
        start: page * pageSize,
        total: totalEl ? parseInt(totalEl.textContent) : 0
      };
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "searchStores",
    value: function searchStores(storeSearchRequest) {
      var _this23 = this;

      var count = storeSearchRequest.count > 0 ? storeSearchRequest.count : 20;
      var start = storeSearchRequest.start > 0 ? storeSearchRequest.start : 0;
      var latitude = storeSearchRequest.latlon.latitude;
      var longitude = storeSearchRequest.latlon.longitude;
      var total;

      if (isNaN(latitude) || isNaN(longitude)) {
        throw new Error('Provide Latitude and Longitude coordinates');
      }

      var getStoresUrl = "".concat(this.dondeApiBasePath, "/near?center=").concat(latitude, ",").concat(longitude, "&dondeKey=56c3681586312e000e000001&_=1527268304391");
      return this.agent.get(getStoresUrl).then(function (res) {
        return res.body;
      }).then(function (jsonRes) {
        total = jsonRes.length;
        var stores = jsonRes.map(function (store) {
          var storeId = store.llp_url.replace('.html', '').substring(1);
          return _this23.getStore(storeId).then(function (data) {
            return data;
          });
        });
        return Promise.all(stores).then(function (stores) {
          var storesToReturn;
          var totalPages = Math.ceil(total / count);

          if (totalPages === start + 1) {
            // on last page
            var remainder = total % count;

            if (remainder > 0) {
              count = remainder;
            }

            storesToReturn = stores.slice(stores.length - remainder);
          } else if (totalPages < start + 1) {
            throw new Error('No stores found');
          } else {
            // middle pages
            storesToReturn = stores.slice(start * count, (start + 1) * count);
          }

          return {
            count: count,
            start: start,
            total: total,
            stores: storesToReturn
          };
        });
      });
    }
    /**
     * @inheritDoc
     */

  }, {
    key: "parseOrder",
    value: function parseOrder(htmlDoc, id) {
      var status = htmlDoc.querySelector('.order-status').textContent;
      var creationDateText = htmlDoc.querySelector('.order-date date').textContent;
      var creationDate = new Date(creationDateText); // Order Items and Totals

      var orderItemsEl = htmlDoc.getElementById('my-orders-table'); // Shipping/Billing Addresses

      var shippingAddressEl = htmlDoc.querySelector('.box-order-shipping-address address');
      var billingAddressEl = htmlDoc.querySelector('.box-order-billing-address address'); // Parse various sub units of order information.

      var parseElPrice = function parseElPrice(el) {
        return parseFloat(el.textContent.slice(1));
      };

      var totals = {
        discounts: 0,
        // TODO: This will be completed when we add coupons to the API.
        shipping: parseElPrice(orderItemsEl.querySelector('.shipping .price')),
        subtotal: parseElPrice(orderItemsEl.querySelector('.subtotal .price')),
        tax: parseElPrice(orderItemsEl.querySelector('.totals-tax .price')),
        total: parseElPrice(orderItemsEl.querySelector('.grand_total .price'))
      };

      var parseItem = function parseItem(itemEl) {
        return {
          id: itemEl.querySelector('.sku').textContent.trim(),
          productId: itemEl.querySelector('.sku').textContent.trim(),
          productName: itemEl.querySelector('.sku').textContent.trim(),
          baseItemPrice: parseElPrice(itemEl.querySelector('.price .price')),
          baseLinePrice: parseElPrice(itemEl.querySelector('.subtotal .price')),
          itemPrice: parseElPrice(itemEl.querySelector('.price .price')),
          linePrice: parseElPrice(itemEl.querySelector('.subtotal .price')),
          quantity: parseInt(itemEl.querySelector('.qty .content').textContent.trim())
        };
      };

      var itemsEls = orderItemsEl.querySelectorAll('tbody tr');

      var items = itemsEls && _toConsumableArray(itemsEls).map(parseItem);

      var shippingAddressParts = shippingAddressEl && shippingAddressEl.textContent.split('\n');
      var shippingAddress = shippingAddressParts && {
        firstName: shippingAddressParts[0].split(' ')[0],
        lastName: shippingAddressParts[0].split(' ')[1],
        phone: shippingAddressParts[8].replace('T: ', ''),
        addressLine1: shippingAddressParts[2],
        addressLine2: shippingAddressParts[3],
        city: shippingAddressParts[6].split(',')[0],
        countryCode: shippingAddressParts[7],
        stateCode: shippingAddressParts[6].split(',')[1],
        postalCode: shippingAddressParts[6].split(', ')[2] // NOTE: Known issue, we can't get the proper code for country and state, only the
        // values.

      };
      var billingAddressParts = billingAddressEl && billingAddressEl.textContent.split('\n');
      var billingAddress = billingAddressParts && {
        firstName: billingAddressParts[0].split(' ')[0],
        lastName: billingAddressParts[0].split(' ')[1],
        phone: billingAddressParts[8].replace('T: ', ''),
        addressLine1: billingAddressParts[2],
        addressLine2: billingAddressParts[3],
        city: billingAddressParts[6].split(',')[0],
        countryCode: billingAddressParts[7],
        // NOTE: Should be a code not a value.
        stateCode: billingAddressParts[6].split(',')[1],
        // NOTE: Should be a code not a value.
        postalCode: billingAddressParts[6].split(', ')[2] // Create a fake payment method id to allow correct mapping of the payment name

      };
      var payments = [{
        id: 'mine',
        amount: totals.total,
        methodId: 'checkmo'
      }]; // Create a fake shipping method id to allow correct mapping of the shipping name

      var selectedShippingMethodId = 'mine';
      return _objectSpread({
        id: id,
        billingAddress: billingAddress,
        creationDate: creationDate,
        items: items,
        status: status,
        payments: payments,
        selectedShippingMethodId: selectedShippingMethodId,
        shippingAddress: shippingAddress
      }, totals);
    }
    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "createOrder",
    value: function createOrder(cart) {
      var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      var paymentMethod = {
        method: cart.payments[0].methodId
      };
      return this.agent.put("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/order")).send(paymentMethod).then(function (res) {
        return _objectSpread({}, cart, {
          id: res.body,
          creationDate: new Date(),
          status: 'Pending'
        });
      });
    }
    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "getOrder",
    value: function getOrder(id) {
      var _this24 = this;

      var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var url = "".concat(this.basePath, "/sales/order/view/order_id/").concat(id, "/");
      return this.agent.get(url).then(function (res) {
        return _this24.buildDocument(res);
      }).then(function (htmlDoc) {
        var isLoginPage = !!htmlDoc.querySelector('body.customer-account-login');
        var isHistoryPage = !!htmlDoc.querySelector('body.sales-order-history'); // If we were redirected to the account order history page, it means that
        // you need to log in to see this order.

        if (isLoginPage) {
          throw new errors.NotAuthenticatedError();
        } // If we were redirected to the account order history page, it means that
        // the order we were looking for wasn't found.


        if (isHistoryPage) {
          throw new errors.NotFoundError('Order Not Found');
        }

        return htmlDoc;
      }).then(function (htmlDoc) {
        return _this24.parseOrder(htmlDoc, id);
      });
    }
    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "getOrders",
    value: function getOrders(ids) {
      var _this25 = this;

      var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var getOrderPromises = ids.map(function (id) {
        return _this25.getOrder(id)["catch"](function () {
          return undefined;
        });
      });
      return Promise.all(getOrderPromises).then(function (orders) {
        var validOrders = orders.filter(function (product) {
          return !!product;
        });
        return {
          count: validOrders.length,
          data: validOrders,
          total: validOrders.length
        };
      });
    }
    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "addCouponEntry",
    value: function addCouponEntry(cart, couponCode) {
      var _this26 = this;

      var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!couponCode) {
        throw new errors.InvalidArgumentError("Parameter 'couponCode' is required");
      }

      return this.agent.put("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/coupons/").concat(couponCode)).then(function () {
        return _this26.getCart(cart.id);
      })["catch"](function () {
        throw new errors.ServerError('Could Not Add Coupon');
      });
    }
    /**
     * @inheritDoc
     */
    // eslint-disable-next-line no-unused-vars

  }, {
    key: "removeCouponEntry",
    value: function removeCouponEntry(cart, couponEntryId) {
      var _this27 = this;

      var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

      if (!cart) {
        throw new errors.InvalidArgumentError("Parameter 'cart' is required");
      }

      if (!couponEntryId) {
        throw new errors.InvalidArgumentError("Parameter 'couponEntryId' is required");
      } // Transform coupon into something the server understands.


      var coupon = {
        coupon_item_id: couponEntryId
      };
      return this.agent["delete"]("".concat(this.basePath, "/rest/default/V1/").concat(this.hasAuthorization() ? 'carts/mine' : "guest-carts/".concat(cart.id), "/coupons")).send(coupon).then(function () {
        return _this27.getCart(cart.id);
      })["catch"](function () {
        throw new errors.ServerError('Could Not Remove Coupon');
      });
    }
  }], [{
    key: "fromConfig",
    value: function fromConfig(config) {
      return new this.prototype.constructor(config);
    }
  }]);

  return MerlinsConnector;
}(_scrapingConnector.ScrapingConnector);

exports.MerlinsConnector = MerlinsConnector;