/**
 * @file
 * Defines a backwards-compatible shim for jquery.cookie.
 */

/**
 * The core/js-cookie library object.
 *
 * @global
 *
 * @var {object} Cookies
 */

(($, Drupal, cookies) => {
  /**
   * Determines if an object is a function.
   *
   * @param {Object} obj
   *   The object to check.
   *
   * @return {boolean}
   *   True if the object is a function.
   */
  const isFunction = obj =>
    Object.prototype.toString.call(obj) === '[object Function]';

  /**
   * Decodes cookie value for compatibility with jquery.cookie.
   *
   * @param {string} value
   *   The cookie value to parse.
   * @param {boolean} parseJson
   *   Whether cookie value should be parsed from JSON.
   *
   * @return {string}
   *   The cookie value for the reader to return.
   */
  const parseCookieValue = (value, parseJson) => {
    if (value.indexOf('"') === 0) {
      value = value
        .slice(1, -1)
        .replace(/\\"/g, '"')
        .replace(/\\\\/g, '\\');
    }

    try {
      value = decodeURIComponent(value.replace(/\+/g, ' '));
      return parseJson ? JSON.parse(value) : value;
    } catch (e) {
      // Exceptions on JSON parsing should be ignored.
    }
  };

  /**
   * Wraps the cookie value to support unsanitized values.
   *
   * Decoding strings is the job of the converter when using js-cookie, and
   * the shim uses the same decode function as that library when the deprecated
   * raw option is not used.
   *
   * @param {string} cookieValue
   *   The cookie value.
   * @param {string} cookieName
   *   The cookie name.
   * @param {reader~converterCallback} converter
   *   A function that takes the cookie value for further processing.
   * @param {boolean} readUnsanitized
   *   Uses the unsanitized value when set to true.
   * @param {boolean} parseJson
   *   Whether cookie value should be parsed from JSON.
   *
   * @return {string}
   *   The cookie value that js-cookie will return.
   */
  const reader = (
    cookieValue,
    cookieName,
    converter,
    readUnsanitized,
    parseJson,
  ) => {
    const value = readUnsanitized
      ? cookieValue
      : parseCookieValue(cookieValue, parseJson);

    if (converter !== undefined && isFunction(converter)) {
      return converter(value, cookieName);
    }

    return value;
  };

  /**
   * Gets or sets a browser cookie.
   *
   * @example
   * // Returns 'myCookie=myCookieValue'.
   * $.cookie('myCookie', 'myCookieValue');
   * @example
   * // Returns 'myCookieValue'.
   * $.cookie('myCookie');
   *
   * @example
   * // Returns the literal URI-encoded value of {"key": "value"} as the cookie
   * // value along with the path as in the above example.
   * $.cookie('myCookie', { key: 'value' });
   * @example
   * $.cookie.json = true;
   * // Returns { key: 'value' }.
   * $.cookie('myCookie');
   *
   * @param {string} key
   *   The name of the cookie.
   * @param {string|Object|Function|undefined} value
   *   A js-cookie converter callback when used as a getter. This callback must
   *   be a function when using this shim for backwards-compatibility with
   *   jquery.cookie. When used as a setter, value is the string or JSON object
   *   to be used as the cookie value.
   * @param {Object|undefined} options
   *   Overrides the default options when used as a setter. See the js-cookie
   *   library README.md file for details.
   *
   * @return {string}
   *   Returns the cookie name, value, and other properties based on the
   *   return value of the document.cookie setter.
   *
   * @see https://www.drupal.org/node/3104677
   * @see https://github.com/js-cookie/js-cookie/blob/v3.0.0-rc.0/README.md
   */
  $.cookie = (key, value = undefined, options = undefined) => {
    // Key should be only encoded if it exists and when not in a raw mode.
    key = key && !$.cookie.raw ? encodeURIComponent(key) : key;
    if (value !== undefined && !isFunction(value)) {
      // The caller is setting a cookie value and not trying to retrieve the
      // cookie value using a converter callback.
      const attributes = Object.assign({}, $.cookie.defaults, options);

      if (typeof attributes.expires === 'string' && attributes.expires !== '') {
        attributes.expires = new Date(attributes.expires);
      }

      const cookieSetter = cookies.withConverter({
        write: cookieValue => encodeURIComponent(cookieValue),
      });

      value =
        $.cookie.json && !$.cookie.raw ? JSON.stringify(value) : String(value);

      return cookieSetter.set(key, value, attributes);
    }

    // Use either js-cookie or pass in a converter to get the raw cookie value,
    // which has security implications, but remains in place for
    // backwards-compatibility.
    const userProvidedConverter = value;
    const cookiesShim = cookies.withConverter({
      read: (cookieValue, cookieName) =>
        reader(
          cookieValue,
          cookieName,
          userProvidedConverter,
          $.cookie.raw,
          $.cookie.json,
        ),
    });

    if (key !== undefined) {
      return cookiesShim.get(key);
    }

    const results = cookiesShim.get();
    Object.keys(results).forEach(resultKey => {
      if (results[resultKey] === undefined) {
        delete results[resultKey];
      }
    });

    return results;
  };

  /**
   * @prop {Object} defaults
   *   The default options when setting a cookie.
   * @prop {string} defaults.path
   *   The default path for the cookie is ''.
   * @prop {undefined} defaults.expires
   *   There is no default value for the expires option. The default expiration
   *   is set to an empty string.
   */
  $.cookie.defaults = Object.assign({ path: '' }, cookies.defaults);

  /**
   * @prop {boolean} json
   *   True if the cookie value should be parsed as JSON.
   */
  $.cookie.json = false;

  /**
   * @prop {boolean} json
   *   True if the cookie value should be returned as-is without decoding
   *   URI entities. In jquery.cookie, this also would not encode the cookie
   *   name, but js-cookie does not allow this.
   */
  $.cookie.raw = false;

  /**
   * Removes a browser cookie.
   *
   * @param {string} key
   *   The name of the cookie.
   * @param {Object} options
   *   Optional options. See the js-cookie library README.md for more details.
   *
   * @return {boolean}
   *   Returns true when the cookie is successfully removed.
   *
   * @see https://www.drupal.org/node/3104677
   * @see https://github.com/js-cookie/js-cookie/blob/v3.0.0-rc.0/README.md
   */
  $.removeCookie = (key, options) => {
    cookies.remove(key, Object.assign({}, $.cookie.defaults, options));
    return !cookies.get(key);
  };
})(jQuery, Drupal, window.Cookies);
