/*
 * Please see wp-cookie-allow.php for more details.
 *
 * @author $Author: Vincent Weber <weepie-plugins@outlook.com> $
 * @since 2.3.3
 */

window.WPCAFR = (function ($) {
  "use strict";
  var front = {
    // declare public params
    done: false,
  };

  // declare private params
  var layoutRequest = null,
    loadingHtml = false,
    ajaxActionRenderLayout = "wpca-frontend-render-layout",
    restActionRenderLayout = "fetch-layout",
    classShow = "wpca-show",
    classHide = "wpca-hide",
    classBtn = "wpca-btn",
    idWrapper = "wpca-lay-out-wrapper",
    idLayer = "wpca-trans-layer",
    statusHide = "hide",
    statusShow = "show",
    cssIdPopupModal = "wpca-popup-modal",
    cssIdPlaceholderTmpl = "wpca-placeholer-html",
    cssIdPlaceholderPrefix = "wpca-replacement-elem-",
    cssClassIconCloseX = "wpca-icon-close",
    cssIdIconSettings = "wpca-icon-settings",
    cssIdIconReconsider = "wpca-reconsider-icon",
    cssClassPlaceholderTxt = "wpca-replace-txt",
    cssClassPosTop = "wpca-top",
    cssClassCenteredBox = "wpca-ct",
    cssClassCcBox = "wpca-cc-sett-box",
    cssClassCcBoxOptions = "wpca-cc-sett-options",
    cssClassCcBoxBtnSave = "wpca-btn-cc-save-sett",
    cssClassBtnAccept = "wpca-btn-accept",
    cssClassBtnDecline = "wpca-btn-decline",
    cssClassBtnResetConsent = "wpca-btn-reset-consent",
    cssClassBtnCcSettings = "wpca-btn-cc-settings",
    cssClassblocked = "wpca-blocked",
    cssClassBtnHide = "wpca-btn-hide",
    cssClassBtnHasReset = "wpca-btn-reset-has",
    elemNameBlock = "wpca-block",
    eventBaseNamespace = "wpca",
    scriptTypeBlocked = "text/template",
    dataKeyTxt = "txt",
    dataKeyTxtProcessing = "txt-processing",
    dataKeyMarked = "wpca-marked",
    dataKeyMarkedAuto = "wpca-marked-auto",
    dataKeyMarkedData = "wpca-marked-data",
    dataKeySkipBckp = "wpca-skip-bckp",
    dataKeyCcCat = "cat",
    dataKeyCcBoxId = "id",
    timeoutProcessing = 200,
    animateDuration = 200,
    layout = "bar",
    yPos = "top",
    posIn = 0,
    posOut = 0,
    cookieExpire = 30,
    hasWPadminbar = false,
    isCenteredBox = false,
    consentMethod = 0,
    hasClientCc = false,
    hasLayer = false,
    hasPopup = false,
    hasCloseEl = false,
    hasCloseElBarBox = false,
    hasReconsiderIcon = false,
    useXAsDismiss = false,
    minScrollTop = 200,
    clientCc = [],
    hasChangedCat = false,
    hasCCPA = false,
    gtmTimeout = 200,
    init3rdPartyTimeout = 200,
    init3rdPartyItemProps = ["type", "cc", "selector", "jsFunc"],
    states = {
      barbox: null,
      accepting: false,
      declining: false,
      resetting: false,
      savingcc: false,
      showingLayer: false,
      showingPopup: false,
    },
    elem = {
      wrapper: {},
      barbox: {},
      ccBox: {},
      wpadminbar: {},
      layer: {},
      privacyBtns: {},
      privacyBtn: {},
      closeBarBox: {},
      closePopup: {},
      popup: {},
      reconsiderIcon: {},
    };

  /**
   * Custom events
   */
  var cEvents = {
    /**
     * The names of the events
     */
    names: {
      init_ready: "wpca.init:ready",
      consent_after: "wpca.consent:after",
      decline_after: "wpca.decline:after",
      reset_after: "wpca.reset:after",
      cc_saved: "wpca.cc:saved",
      tp_init_before: "wpca.3rdpartyinit:before",
      tp_init_after: "wpca.3rdpartyinit:after",
      init_blocking_ready: "wpca.initblocking:ready",
      init_states_before: "wpca.initstates:before",
      popup_show: "wpca.popup:show",
      popup_hide: "wpca.popup:hide",
    },
    /**
     * Get the name of an event by index
     *
     * @param {String} idx
     *
     * @returns {String}
     */
    getName: function (idx) {
      var event = "",
        i;
      if ("string" !== typeof idx) {
        return event;
      }
      for (i in this.names) {
        if (idx === i) {
          event = this.names[i];
          break;
        }
      }
      return event;
    },
    /**
     * Trigger an event
     *
     * @returns void
     */
    trigger: function () {
      var allowedEv = false,
        event = "",
        i;
      if (0 < arguments.length) {
        var idx = arguments[0];
        if ("string" !== typeof idx) {
          idx = "";
        }
        if (1 < arguments.length) {
          [].shift.apply(arguments);
        }
      }
      event = this.getName(idx);
      if ("" !== event) {
        $(window).triggerHandler(event, arguments);
      }
    },
  };

  var events = {
    /**
     * Unbind all events
     *
     * @returns events
     */
    unbind: function () {
      $(window).off(cEvents.names.init_blocking_ready, handlerBlockingReady);
      $(window).off(cEvents.names.consent_after, handlerConsentAfter);
      $(window).off(cEvents.names.decline_after, handlerDeclineAfter);
      $(window).off(cEvents.names.reset_after, handlerResetAfter);
      getProcessingButtons().off("click", handlerProcessingButtonClick);
      $("body").off(
        "click",
        "." + cssClassBtnAccept,
        handlerConsentClickAccept
      );
      $("body").off(
        "click",
        "." + cssClassBtnDecline,
        handlerConsentClickDecline
      );
      $("body").off(
        "click",
        "." + cssClassBtnResetConsent,
        handlerConsentClickResetConsent
      );
      $("body").off(
        "click",
        "." + cssClassBtnCcSettings + ", #" + cssIdIconSettings,
        handlerCcClickOpenSett
      );
      elem.closeBarBox.off("click", handlerClickCloseBarBox);
      elem.layer.off("click", handlerClickClosePopup);
      elem.closePopup.off("click", handlerClickClosePopup);
      $(document).off("keyup", handlerDocumentKeyup);
      $(window).off("click", handlerConsentClickWindow);
      $("." + cssClassCcBoxOptions + " input", "." + cssClassCcBox).off(
        "click change",
        handlerCcClickInput
      );
      $("." + cssClassCcBoxBtnSave, "." + cssClassCcBox).off(
        "click",
        handlerCcClickSaveSett
      );

      return this;
    },
    /**
     * Bind all events
     *
     * @returns events
     */
    bind: function (unbind) {
      if (unbind) {
        this.unbind();
      }

      // attach default logic for the wpca.initblocking:ready event
      $(window).on(cEvents.names.init_blocking_ready, handlerBlockingReady);
      // attach default logic for the wpca.consent:after event
      $(window).on(cEvents.names.consent_after, handlerConsentAfter);
      // attach default logic for the wpca.decline:after event
      $(window).on(cEvents.names.decline_after, handlerDeclineAfter);
      // attach default logic for the wpca.reset:after event
      $(window).on(cEvents.names.reset_after, handlerResetAfter);

      // attach default logic for all processing buttons
      getProcessingButtons().on("click", handlerProcessingButtonClick);
      // do not attach click accept button event if consent method is "Click/Scroll", "Only Click" or "Only Scroll"
      // do attach if accept buttons are needed for the CCPA
      if (hasConsentMethodForBtnAccept() || front.needButtonAcceptForCCPA()) {
        // attach click event to the cookie accept button
        $("body").on(
          "click",
          "." + cssClassBtnAccept,
          handlerConsentClickAccept
        );
      }
      // attach click event to the cookie decline button
      $("body").on(
        "click",
        "." + cssClassBtnDecline,
        handlerConsentClickDecline
      );
      // attach click event to the cookie reset button
      $("body").on(
        "click",
        "." + cssClassBtnResetConsent,
        handlerConsentClickResetConsent
      );
      // attach click event to the cookie settings button
      $("body").on(
        "click",
        "." + cssClassBtnCcSettings + ", #" + cssIdIconSettings,
        handlerCcClickOpenSett
      );

      // take care of the scroll events
      addScrollEventListener(handlerScrollWindow, {});

      // attach click events for the close X elemeents
      if (hasCloseElBarBox) {
        elem.closeBarBox.on("click", handlerClickCloseBarBox);
      }
      if (hasPopup) {
        // close the popup when when clicking the layer or closing x
        elem.layer.on("click", handlerClickClosePopup);
        elem.closePopup.on("click", handlerClickClosePopup);
        // close the popup also when hitting the esc key
        $(document).on("keyup", handlerDocumentKeyup);
      }

      if (front.isFirstVisit()) {
        if (!hasCCPA && consentMethod) {
          // 1 accept button
          // 2 accept button / scroll
          // 3 accept button / click
          // 4 accept button / click scroll
          // 5 click scroll
          // 6 only scroll
          // 7 only click

          // only click (NO accept button)
          if (7 === consentMethod) {
            $(window).on("click", handlerConsentClickWindow);
            // only scroll (NO accept button)
          } else if (6 === consentMethod && !front.accepted()) {
            addScrollEventListener(handlerConsentscrollWindow);
            // take a click or scroll event as an 'Accept' cookie consent
          } else if (5 === consentMethod && !front.accepted()) {
            $(window).on("click", handlerConsentClickWindow);
            addScrollEventListener(handlerConsentscrollWindow);
            // take a click or scroll event as an 'Accept' cookie consent
          } else if (4 === consentMethod && !front.accepted()) {
            $(window).on("click", handlerConsentClickWindow);
            addScrollEventListener(handlerConsentscrollWindow);
            // only click (and accept button)
          } else if (3 === consentMethod && !front.accepted()) {
            $(window).on("click", handlerConsentClickWindow);
            // only scroll (and accept button)
          } else if (2 === consentMethod && !front.accepted()) {
            addScrollEventListener(handlerConsentscrollWindow);
          }
        } else if (hasCCPA && wpcaData.ccpa.consentAfterUse) {
          $(window).on("click", handlerConsentClickWindow);
          addScrollEventListener(handlerConsentscrollWindow);
        }
      }

      // cookie category settings
      $("." + cssClassCcBoxOptions + " input", "." + cssClassCcBox).on(
        "click change",
        handlerCcClickInput
      );
      $("." + cssClassCcBoxBtnSave, "." + cssClassCcBox).on(
        "click",
        handlerCcClickSaveSett
      );

      // take care of the resize event
      $(window).on("resize", function () {
        // re-init position params/ CSS on resizing
        initPosParams(states.barbox);
        initStatus(states.barbox);
      });

      return this;
    },
  };

  var obzerver = {
    /**
     * @var MutationObserver
     */
    MutationObzerverObj: null,
    ready: false,
    obzervering: false,
    /**
     * Click event callback for MutationObserver instance
     *
     * @param {Array.<MutationRecord>} mutationsList
     */
    handlerMutationObzerver: function (mutationsList) {
      try {
        var i,
          mutation,
          $removed,
          data = {},
          skip = false;

        for (i = 0; i < mutationsList.length; i++) {
          mutation = mutationsList[i];
          if ("childList" === mutation.type) {
            // removing
            if (mutation.removedNodes.length) {
              $removed = $(mutation.removedNodes[0]);
              if ($removed.isMarked()) {
                data = $removed.data(dataKeyMarkedData);
                skip = $removed.data(dataKeySkipBckp);
                // only backup in the body
                if ("body" === data.context && true !== skip) {
                  // mark as backup
                  data["backup"] = true;
                  $removed
                    .setMarkedDataObj(data)
                    .insertAfter($(mutation.previousSibling))
                    .hide();
                }
              }
            }
          }
        }
      } catch (exc) {
        WPIE.log(exc.message, exc);
      }
    },
    /**
     * Init the MutationObserver
     *
     * @returns {this}
     */
    init: function () {
      if (!this.ready) {
        this.MutationObzerverObj = new MutationObserver(
          this.handlerMutationObzerver
        );
        this.ready = true;
      }
      return this;
    },
    /**
     * Start observing
     *
     * @returns {this}
     */
    obzerve: function () {
      if (this.ready) {
        this.MutationObzerverObj.observe(window.document.body, {
          attributes: true,
          childList: true,
          subtree: true,
        });
        this.obzervering = true;
      }
      return this;
    },
    /**
     * Disconnect the observer
     *
     * @returns {this}
     */
    disconnect: function () {
      if (this.obzervering) {
        this.MutationObzerverObj.disconnect();
        this.obzervering = false;
      }
      return this;
    },
  };

  // extend
  var extend = {
    /**
     * Check if current element is a processing element
     */
    isProcessingBtn: function () {
      return (
        $(this).hasClass(classBtn) &&
        $(this).is("[data-" + dataKeyTxt + "]") &&
        $(this).is("[data-" + dataKeyTxtProcessing + "]")
      );
    },
    processingBntState: function (state) {
      var btn = $(this);
      if (btn.isProcessingBtn()) {
        if ("process" === state) {
          btn
            .css({ height: btn.outerHeight() + "px" })
            .html(btn.data(dataKeyTxtProcessing))
            .trigger("focus");
        } else {
          btn.html(btn.data(dataKeyTxt));
        }
      }
      return btn;
    },
    /**
     * Check if current element is marked for blocking
     */
    isMarked: function () {
      return (
        $(this).is("[data-" + dataKeyMarked + "]") &&
        $(this).is("[data-" + dataKeyMarkedData + "]")
      );
    },
    /**
     * Get marked data
     *
     * @param {String} k
     *
     * @returns {Mixed} or null on failure
     */
    getMarkedData: function (k) {
      if ("undefined" === typeof k) {
        return null;
      }
      if ($(this).isMarked()) {
        var data = $(this).data(dataKeyMarkedData);
        return "undefined" !== typeof data[k] ? data[k] : null;
      } else {
        return null;
      }
    },
    /**
     * Set marked data object
     *
     * @param {String} data
     * @param {Boolean} force
     *
     * @returns {Object}
     */
    setMarkedDataObj: function (data, force) {
      if ("undefined" === typeof data || "object" !== typeof data) {
        return $(this);
      }

      return this.each(function () {
        if ($(this).isMarked()) {
          var k,
            _data = $(this).data(dataKeyMarkedData);
          for (k in data) {
            var v = data[k];
            _data[k] = v;
          }
          $(this)
            .data(dataKeyMarkedData, _data)
            .attr("data-" + dataKeyMarkedData, JSON.stringify(_data));
        } else if (true === force) {
          $(this)
            .attr("data-" + dataKeyMarked, "1")
            .attr("data-" + dataKeyMarkedData, JSON.stringify(data))
            .data(dataKeyMarked, "1")
            .data(dataKeyMarkedData, data);
        }
      });
    },
    /**
     * Check a checkbox
     *
     * @param {boolean|number} status
     *
     * @returns {Object} the jQuery current element instance for chaining
     */
    cbCheck: function (status) {
      if ("undefined" === typeof status) {
        status = true;
      }
      if ("boolean" !== typeof status) {
        if (0 === parseInt(status)) {
          status = false;
        } else if (1 === parseInt(status)) {
          status = true;
        }
      }
      $(this).prop("checked", status);

      // allow chaining
      return $(this);
    },
    /**
     * Check checkboxes in a cc settings box for given categories
     *
     * @param {boolean} check
     * @param {Object[]} categories
     *
     * @returns {Object|void} the jQuery current element instance for chaining or void
     */
    cbCheckForCategory: function (check, categories) {
      if ("undefined" === typeof check) {
        return $(this);
      } else if ("string" !== typeof categories && !Array.isArray(categories)) {
        return $(this);
      } else if (!$(this).hasClass(cssClassCcBox)) {
        return $(this);
      }

      if (!Array.isArray(categories)) {
        categories = [categories];
      }

      for (var i = 0; i < categories.length; i++) {
        var cat = categories[i],
          ccBox = $(this),
          cb = ccBox.find(
            "." + cssClassCcBoxOptions + ' input[data-cat="' + cat + '"]'
          );

        if (cb.length) {
          if (check && !cb.is(":checked")) {
            cb.cbCheck();
          } else if (!check && cb.is(":checked")) {
            cb.cbCheck(0);
          }
        }
      }
    },
    /**
     * Hide/show a Button
     *
     * @param {boolean} status
     *
     * @returns {Object} the jQuery current element instance for chaining
     */
    hideButton: function (status) {
      if ("undefined" === typeof status) {
        status = false;
      }
      if ("boolean" !== typeof status) {
        if (0 === parseInt(status)) {
          status = false;
        } else if (1 === parseInt(status)) {
          status = true;
        }
      }
      if (status) {
        $(this).addClass(cssClassBtnHide);
      } else {
        $(this).removeClass(cssClassBtnHide);
      }

      // allow chaining
      return $(this);
    },
    /**
     * Determine if current element has a placholder
     *
     * @returns {Boolean}
     */
    hasPlaceholder: function (c) {
      return 0 <= c && 0 < $("#" + cssIdPlaceholderPrefix + c).length;
    },
  };

  function doFn(fn, args, rtrn) {
    var fnAsStr = "string" === typeof fn;
    var argsNotArr = !Array.isArray(args);

    if ("function" !== typeof fn && !fnAsStr) {
      throw new Error("doFn() argument (fn) is not a valid function");
    }

    if (argsNotArr && "undefined" !== typeof args) {
      args = [args];
    } else if (argsNotArr) {
      args = [];
    }

    var func = (fnAsStr && window[fn]) || fn;

    if (args.length > 0) {
      if (!rtrn) {
        func.apply(this, args);
      } else {
        return func.apply(this, args);
      }
    } else {
      if (!rtrn) {
        func.call();
      } else {
        return func.call();
      }
    }
  }

  function waitForDependency(args) {
    if (!args || "string" !== typeof args.depName || "" === args.depName) {
      throw new Error("depName argument is not valid");
    }

    return new Promise(function (resolve, reject) {
      setTimeout(
        async function (args) {
          if (args.depName && window[args.depName]) {
            args.resolved = true;
            return resolve(args);
          } else {
            args.maxRetries = args.maxRetries - 1;
            var _args = await waitForDependency(args);

            if (_args.resolved) {
              return resolve(_args);
            }
            if (0 === _args.maxRetries) {
              return reject(
                `dependency ${_args.depName} not found and max retries (${_args.maxRetries}) reached`
              );
            }
          }
        },
        args.timeout,
        args
      );
    });
  }

  async function doFnWhenDependencyReady(
    depName,
    func,
    funcArgs,
    timeout,
    maxRetries
  ) {
    return waitForDependency({
      depName,
      func,
      funcArgs,
      timeout,
      maxRetries,
    }).then(function (args) {
      doFn(args.func, args.funcArgs);
    });
  }

  /**
   * Get all processing buttons on the current page
   */
  function getProcessingButtons() {
    return $(`.${classBtn}[data-${dataKeyTxt}][data-${dataKeyTxtProcessing}]`);
  }

  /**
   * Set all processing button to their default text
   */
  function setProcessingButtonDefault() {
    var btns = getProcessingButtons();

    if (btns.length > 0) {
      btns.each(function () {
        $(this).processingBntState("default");
      });
    }
  }

  /**
   * Get the width and height of an jQuery DOM object
   *
   * The obj Object will be filled with the width and height values
   *
   * @param {Object.<jQuery>} $el
   * @param {Object} obj
   * @param {Boolean} withTempElem
   *
   * @returns {Boolean} true if valid width and height found
   */
  function getWidthHeight($el, obj, withTempElem) {
    var w = 0,
      h = 0,
      $tmpEl;

    try {
      if (!($el instanceof jQuery)) {
        throw new Error('param "$el" needs to be a jQuery Object.');
      }
      if ("object" !== typeof obj) {
        throw new Error(
          'param "obj" needs to be an Object to return width and height.'
        );
      }

      // temporarily add an element to calculate the outer elements width
      // this will hopefully get the elements dimensions
      if (withTempElem) {
        $tmpEl = $('<div class="wpca-tmp"/>')
          .css({
            display: "block",
            width: "100%",
            height: "1px",
            visibility: "hidden",
          })
          .appendTo($el);
        $el = $el.find(".wpca-tmp");
      }

      // try to get the w and h from the attributes
      w = $el.attr("width");
      h = $el.attr("height");

      // if failed, try to get the w h from the css
      if (undefined === w || undefined === h) {
        w = $el[0].clientWidth;
        h = $el[0].clientHeight;
      }

      // if is an image, try to get the size from the filename
      if ((!h || h === 1) && $el.is("img")) {
        var src = $el.getMarkedData("src");
        var wxh = src.match(/-(\d+)x(\d+)\.[a-z]+$/i);
        if (wxh) {
          w = wxh[1];
          h = wxh[2];
        }
      }
    } catch (exc) {
      obj = {};
      WPIE.log(exc.message);
    } finally {
      if (withTempElem) {
        $tmpEl.remove();
      }

      obj.w = parseInt(w);
      obj.h = parseInt(h);
      return 0 < obj.w && 0 < obj.h;
    }
  }

  /**
   * Set a cookie
   *
   * Wrapper function for WPIE.setKookie()
   *
   * @param {string} name
   * @param {string} value
   * @param {number} days
   * @param {string} domain
   * @param {string} path
   * @param {boolean} secure
   */
  function setKookie(name, value, days, domain, path, secure) {
    if ("undefined" === typeof name || "undefined" === typeof value) {
      throw new Error("Could not set cookie. Name or value is undefined.");
    }
    if ("undefined" === typeof days) {
      days = cookieExpire;
    }
    if ("undefined" === typeof domain) {
      domain = wpcaData.cookieDomain;
    }
    if ("undefined" === typeof path) {
      path = wpcaData.cookiePath;
    }
    if ("undefined" === typeof secure) {
      secure = wpcaData.ssl;
    }

    WPIE.setKookie(name, value, days, domain, path, secure);
  }

  /**
   * Delete a cookie
   *
   * Wrapper function for WPIE.eraseKookie()
   *
   * @param {string} name
   * @param {string} domain
   * @param {string} path
   */
  function deleteKookie(name, domain, path) {
    if ("undefined" === typeof name) {
      throw new Error("Could not delete cookie. Name is undefined.");
    }
    if ("undefined" === typeof domain) {
      domain = wpcaData.cookieDomain;
    }
    if ("undefined" === typeof path) {
      path = wpcaData.cookiePath;
    }
    //TODO check if secure param is needed

    WPIE.eraseKookie(name, domain, path);
  }

  /**
   * Read a cookie
   *
   * Wrapper function for WPIE.readKookie()
   *
   * @param {string} name
   *
   * @returns {string|null} the cookie value or null
   */
  function readKookie(name) {
    return WPIE.readKookie(name);
  }

  /**
   * Determine if having accept buttons
   *
   * @returns {boolean}
   */
  function hasBtnAccept() {
    return $("." + cssClassBtnAccept).length;
  }

  /**
   * Determine if having decline buttons
   *
   * @returns {boolean}
   */
  function hasBtnDecline() {
    return $("." + cssClassBtnDecline).length;
  }

  /**
   * Determine if having reset consent buttons
   *
   * @returns {boolean}
   */
  function hasBtnResetConsent() {
    return $("." + cssClassBtnResetConsent).length;
  }

  /**
   * Determine if having Cookie Category settings buttons
   *
   * @returns {boolean}
   */
  function hasBtnCcSettings() {
    return $("." + cssClassBtnCcSettings).length;
  }

  /**
   * Determine if the consent method supports accepting by button
   *
   * @returns {boolean}
   */
  function hasConsentMethodForBtnAccept() {
    return -1 === [5, 6, 7].indexOf(consentMethod);
  }

  /**
   * Determine if the consent method supports a layer
   *
   * Consent methods 2,3,4,5,6,7 have click, scroll or both
   *
   * @returns {boolean}
   */
  function hasConsentMethodForLayer() {
    return 1 === consentMethod;
  }

  /**
   * Helper function to know if 3 === cookiesBeforeConsent
   *
   * @returns bool
   */
  function do3rdPartyCookiesBeforeConsent() {
    return (
      null != wpcaData.cookiesBeforeConsent &&
      3 === wpcaData.cookiesBeforeConsent
    );
  }

  /**
   * Helper function to determine if given Cookie Category is allowed
   *
   * @param {string} name
   *
   * @returns {boolean}
   */
  function isAllowedCookieCategory(name) {
    return -1 !== wpcaData.allowedCc.indexOf(name);
  }

  /**
   * Helper function to determine if all required Cookie Category are present
   *
   * @param {Object[]} categories
   *
   * @returns bool
   */
  function hasRequiredCookieCategory(categories) {
    var hasArr = [];

    for (var i = 0; i < wpcaData.requiredCc.length; i++) {
      var rcc = wpcaData.requiredCc[i];
      hasArr.push(-1 !== categories.indexOf(rcc));
    }

    var has = -1 === hasArr.indexOf(false);

    return has;
  }

  /**
   * Add the required categories to given array
   *
   * @param {Object[]} categories
   */
  function addRequiredCookieCategories(categories) {
    for (var i = 0; i < wpcaData.requiredCc.length; i++) {
      var rcc = wpcaData.requiredCc[i];
      if (-1 === categories.indexOf(rcc)) {
        categories.push(rcc);
      }
    }
  }

  /**
   * Get selected (checked) cookie category names based on given input elements
   *
   * @param {Object} inputs
   *
   * @throws Error if finds a cookie category that is not allowed
   *
   * @returns {Object[]} the categories
   */
  function getSelectedCookieCategories(inputs) {
    var categories = [];
    if (inputs) {
      inputs.each(function () {
        var cb = $(this),
          cat = cb.data(dataKeyCcCat);

        // check if it is an allowed category
        if (!isAllowedCookieCategory(cat)) {
          throw new Error(cat + " is not an allowed cookie category.");
        }

        // if is checked, add the category
        if (cb.is(":checked")) {
          categories.push(cat);
        }
      });
    }

    return categories;
  }

  /**
   * Show the bar/box
   *
   * @returns void
   */
  function showBarbox() {
    elem.barbox.removeClass(classHide);
    elem.barbox.addClass(classShow);
  }

  /**
   * Hide the bar/box
   *
   * @returns void
   */
  function hideBarbox() {
    elem.barbox.removeClass(classShow);
    elem.barbox.addClass(classHide);
  }

  /**
   * Show the layer
   *
   * @returns void
   */
  function showLayer() {
    if (hasLayer && !states.showingLayer) {
      states.showingLayer = true;
      elem.layer.removeClass(classHide);
      $("html").css("overflow", "hidden");
    }
  }

  /**
   * Hide the layer
   *
   * @returns void
   */
  function hideLayer() {
    if (hasLayer && states.showingLayer) {
      states.showingLayer = false;
      elem.layer.addClass(classHide);
      $("html").css("overflow", "initial");
    }
  }

  /**
   * Show the popup
   *
   * The status of the layer is also handled
   *
   * @returns void
   */
  function showPopup() {
    if (hasPopup) {
      states.showingPopup = true;

      showLayer();

      // add an indicator class to the bar/box
      elem.barbox.addClass("wpca-cc-settings-open");
      elem.popup.removeClass(classHide);

      hideReconsiderIcon();

      cEvents.trigger("popup_show");
    }
  }

  /**
   * Hide the popup
   *
   * The status of the layer is also handled
   *
   * @returns void
   */
  function hidePopup() {
    if (hasPopup) {
      states.showingPopup = false;
      // add an indicator class to the bar/box
      elem.barbox.removeClass("wpca-cc-settings-open");
      elem.popup.addClass(classHide);

      // is user configured to show a layer,
      // do not hide the layer
      if (!front.doLayer()) {
        hideLayer();
      }

      if (!front.needConsentLayout()) {
        showReconsiderIcon();
      }

      cEvents.trigger("popup_hide");
    }
  }

  /**
   * Show the reconsider icon
   *
   * @returns void
   */
  function showReconsiderIcon() {
    if (hasReconsiderIcon) {
      elem.reconsiderIcon.removeClass(classHide);
      elem.reconsiderIcon.addClass(classShow);
    }
  }

  /**
   * Hide the reconsider icon
   *
   * @returns void
   */
  function hideReconsiderIcon() {
    if (hasReconsiderIcon) {
      elem.reconsiderIcon.removeClass(classShow);
      elem.reconsiderIcon.addClass(classHide);
    }
  }

  /**
   * Uncheck all cookie category checkboxes
   *
   * @returns void
   */
  function uncheckCookieCategoriesAll() {
    elem.ccBox.each(function () {
      $(this).cbCheckForCategory(false, wpcaData.allowedCc);
    });
  }

  /**
   * Check only the cookie category checkboxed that are required
   *
   * @returns void
   */
  function checkCookieCategoriesRequiredOnly() {
    uncheckCookieCategoriesAll();

    elem.ccBox.each(function () {
      $(this).cbCheckForCategory(true, wpcaData.requiredCc);
    });
  }

  /**
   * Check all allowed cookie category checkboxed
   *
   * @returns void
   */
  function checkCookieCategoriesAllowed() {
    elem.ccBox.each(function () {
      $(this).cbCheckForCategory(true, wpcaData.allowedCc);
    });
  }

  /**
   * Check only client cookie category checkboxes
   *
   * @returns void
   */
  function checkCookieCategoriesClient() {
    uncheckCookieCategoriesAll();

    elem.ccBox.each(function () {
      $(this).cbCheckForCategory(true, clientCc);
    });
  }

  /**
   * Get the option input elements for the current box based on the clicked button
   *
   * @param {Object} btn
   *
   * @returns {Object}
   */
  function getInputsForBtn(btn) {
    return btn
      .parents("." + cssClassCcBox)
      .find("." + cssClassCcBoxOptions + " input");
  }

  /**
   * Get array with client Cookie Categories from the cookie
   */
  function getClientCookieCategoriesFromCookie() {
    var cookiesStr = readKookie(wpcaData.cookieNameCc);
    if (null != cookiesStr) {
      return cookiesStr.split(",");
    } else {
      return [];
    }
  }

  /**
   * Update the client Cookie and related params
   *
   * @param {Object[]} categories
   *
   * @returns void
   */
  function updateClientCookieAndParams(categories) {
    // if categories is NOT an array, use the required cookies array
    if (!Array.isArray(categories)) {
      categories = [];
    }

    hasClientCc = 0 < categories.length;

    // sort categories
    if (hasClientCc) {
      categories.sort(function (a, b) {
        return wpcaData.allowedCc.indexOf(a) - wpcaData.allowedCc.indexOf(b);
      });
    }

    // get the current categories as an array
    var ccArrCurr = getClientCookieCategoriesFromCookie();
    // get new categories as an comma separated sting
    var ccStrNew = hasClientCc ? categories.join() : "";

    // delete the wpca_cc cookie
    deleteKookie(wpcaData.cookieNameCc);
    // set the wpca_cc cookie
    if (hasClientCc) {
      setKookie(wpcaData.cookieNameCc, ccStrNew);
    }

    // set params
    clientCc = categories;
    hasChangedCat = hasChangedCookieCategories(categories, ccArrCurr);
  }

  /**
   * Compare to arrays and determine if they are different
   *
   * @param {Object[]} arr1
   * @param {Object[]} arr2
   *
   * @returns {boolean}
   */
  function hasChangedCookieCategories(arr1, arr2) {
    var diff = WPIE.arrDiff(arr1, arr2);

    return 0 < diff.length;
  }

  /**
   * Determine if AJAX request will be done
   *
   * @param {Object[]|string} namespaces
   *
   * @returns boolean
   */
  function hasEventWillDoAjaxRequest(namespaces) {
    if ("object" !== typeof namespaces && "string" !== typeof namespaces) {
      return false;
    }

    var events = $(window).data("events"),
      nsEvents;

    if ("undefined" === typeof events) {
      return false;
    } else if ("undefined" === typeof events[eventBaseNamespace]) {
      return false;
    } else if ("string" === typeof namespaces) {
      namespaces = [namespaces];
    }

    nsEvents = events[eventBaseNamespace];
    for (var i = 0; i < nsEvents.length; i++) {
      var ev = nsEvents[i],
        evNamespace = ev.namespace || null;
      for (var ii = 0; ii < namespaces.length; ii++) {
        var ns = namespaces[ii],
          evMatch = evNamespace === ns;
        if (
          evMatch &&
          null != ev.data[willDoAjaxRequest] &&
          ev.data[willDoAjaxRequest]
        ) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Set CSS classes for the element
   *
   * @param {string} state, the state of the element:  hide, show
   *
   * @returns void
   */
  function setStateClasses(state) {
    var _state =
      "undefined" === typeof state || false == state ? statusShow : state;

    switch (_state) {
      case statusHide:
        hideBarbox();
        showReconsiderIcon();
        break;
      case statusShow:
      default:
        hideReconsiderIcon();
        showBarbox();
        break;
    }
  }

  /**
   * Animate the current element
   *
   * Tasks:
   *      - Animating the element position
   *      - Setting CSS classes
   *      - Setting the element position in setStateClasses
   *      - set cookie to remember the clients choice
   *
   * @returns void
   */
  function animate(status) {
    var animSettings = {},
      showing = status === statusShow,
      hiding = status === statusHide;

    // return if the requested status is aleady active
    if ((front.hasLayout() && showing) || (!front.hasLayout() && hiding)) {
      return;
    }

    if (isCenteredBox) {
      animSettings = front.hasLayout()
        ? { top: "-" + posIn + "px" }
        : { top: "50%" };
    } else if ("top" === yPos) {
      animSettings = front.hasLayout()
        ? { top: "-" + posIn + "px" }
        : { top: posOut + "px" };
    } else if ("bottom" === yPos) {
      animSettings = front.hasLayout()
        ? { bottom: "-" + posIn + "px" }
        : { bottom: posOut + "px" };
    }

    // to animate from hiding to showing, the bar/box not be hided
    if (!front.hasLayout() && showing) {
      setStateClasses(statusShow);
    }

    elem.barbox.animate(animSettings, animateDuration, function () {
      if (!front.hasLayout() && showing) {
        //element == visible
        setStateClasses(statusShow);
      } else if (front.hasLayout() && hiding) {
        //element == hided
        setStateClasses(statusHide);
      }
    });
  }

  /**
   * Reload a script by removing and adding it again
   *
   * @param {Object|String} src
   *
   * @returns {Object}
   */
  function reloadScript(elem, src) {
    var $elem, i, $cln, next;

    if ("" !== src) {
      $elem = $('script[src$="' + src + '"]');
    } else {
      $elem = elem;
    }

    // if more elements are found, use the one that is not
    // yet marked for reloading (not having dataKeySkipBckp)
    if ($elem.length > 1) {
      $elem.each(function () {
        if ($(this).isMarked() && !$(this).data(dataKeySkipBckp)) {
          $elem = $(this);
          return false;
        }
      });
    }

    // return if already reloaded
    if (true === $elem.data(dataKeySkipBckp)) {
      return $elem;
    }
    // add data indicating not to backup
    $elem.data(dataKeySkipBckp, true).attr("data-" + dataKeySkipBckp, true);

    // find the next sibling
    next = $elem[0].nextSibling;
    // clone the current element
    $cln = $($elem[0].cloneNode(true));
    // check if next sibling is the <!--[wpca_mrkd]--> comment
    if (next && 8 == next.nodeType && "[wpca_mrkd]" == next.nodeValue) {
      next.parentNode.removeChild(next);
    }
    try {
      // insert the clone after current elememt
      $elem.after($cln);
    } catch (exc) {}

    // then remove the current element
    $elem.remove();
    // insert the comment for aesthetic
    $cln.after(document.createComment("[wpca_mrkd]"));

    // return the cloned (reloaded) element
    return $cln;
  }

  function _doBlockingLogic($elem, c, cc, block) {
    var placeholderHtmlTmpl = wpcaData.doPlaceholder
        ? $("#" + cssIdPlaceholderTmpl).html()
        : "",
      data = $elem.data(dataKeyMarkedData),
      type = $elem.getMarkedData("type"),
      placeholder = $elem.getMarkedData("placeholder"),
      blocked = $elem.getMarkedData("blocked"),
      backup = $elem.getMarkedData("backup"),
      context = $elem.getMarkedData("context"),
      src = $elem.getMarkedData("src"),
      srcset = $elem.getMarkedData("srcset"),
      nodeName = $elem.getMarkedData("nodeName"),
      hasPlaceholder = $elem.hasPlaceholder(c),
      hasSrc = null !== src,
      hasSrcset = null !== srcset,
      ccLabel = (wpcaData.ccLabels && wpcaData.ccLabels[cc]) || cc,
      isScript = $elem.is("script"),
      isIframe = $elem.is("iframe"),
      isIframeNodeReplaced = !$elem.is("iframe") && nodeName === "iframe",
      isImg = $elem.is("img"),
      isLink = $elem.is("a"),
      placeholderHtml = "",
      dim = {},
      $placeholder,
      $elemInner,
      txt;

    // continue if need to block and already blocked
    if (block && blocked) {
      if (placeholder && hasPlaceholder) {
        return;
      }
    }

    // do general logic when not blocking
    if (!block) {
      data.blocked = 0;

      // for elements with a src(set) attribute
      if (hasSrc) {
        if (isLink) {
          $elem.attr("href", src);
        } else if (isIframe) {
          $elem[0].contentWindow.location.replace(src);
        } else {
          $elem.attr("src", src);
        }
      }
      if (hasSrcset) {
        $elem.attr("srcset", srcset);
      }

      // for scripts
      if (isScript) {
        if (
          $elem[0].hasAttribute("type") &&
          scriptTypeBlocked === $elem.attr("type")
        ) {
          $elem.attr("type", "text/javascript");
        }

        if (!hasSrc) {
          // reload the inner content
          $elem = reloadScript($elem, "");
        } else {
          $elem = reloadScript($elem, src);
        }
      }

      if (isIframeNodeReplaced) {
        var attrs = {};
        $.each($elem[0].attributes, function (idx, attr) {
          attrs[attr.nodeName] = attr.nodeValue;
        });
        $elem
          .data(dataKeySkipBckp, true)
          .attr("data-" + dataKeySkipBckp, true)
          .replaceWith(function () {
            attrs.text = $(this).text();
            $elem = $("<iframe />", attrs);
            return $elem;
          });
      }
    } else {
      // for scripts
      data.blocked = 1;
      // restore the blocked sources to allow multiple accept/reset/accept actions
      if (isScript) {
        $elem.attr("type", scriptTypeBlocked);
        if (hasSrc && $elem.attr("src") !== wpcaData.blockedAssetsUri.js) {
          $elem.attr("src", wpcaData.blockedAssetsUri.js);
        }
      } else if (isIframe || isIframeNodeReplaced) {
        if (hasSrc && $elem.attr("src") !== wpcaData.blockedAssetsUri.iframe) {
          $elem[0].contentWindow.location.replace(
            wpcaData.blockedAssetsUri.iframe
          );
        }
      } else if (isImg) {
        if (hasSrc && $elem.attr("src") !== wpcaData.blockedAssetsUri.img) {
          $elem.attr("src", wpcaData.blockedAssetsUri.img);
        }
        if (hasSrcset) {
          $elem.attr("srcset", "");
        }
      } else if (isLink) {
        if (hasSrc && $elem.attr("href") !== wpcaData.blockedAssetsUri.a) {
          $elem.attr("href", wpcaData.blockedAssetsUri.a);
        }
      }
    }

    // do body specific logic
    if ("body" === context) {
      $placeholder = "";
      // when blocking and placeholder is needed
      if (block) {
        if (placeholder && wpcaData.doPlaceholder) {
          // add the current counter as elem id
          data.id = c;
          // show placeholer
          placeholderHtml = placeholderHtmlTmpl
            .replace(/cc="%CC%/g, `cc="${cc}`)
            .replace(/wpca-replace-%CC%/g, `wpca-replace-${cc}`)
            .replace(/%TYPE%/g, type)
            .replace(/%CC%/g, ccLabel);

          // construct a jQuery dom object for the placeholder element
          $placeholder = $(placeholderHtml);
          $placeholder.attr("id", cssIdPlaceholderPrefix + c);

          // replace custom text if given
          if ("" !== (txt = $elem.getMarkedData("txt"))) {
            $placeholder.find("." + cssClassPlaceholderTxt).html(txt);
          }

          // try to find the width and height of element or inner elements
          if (!getWidthHeight($elem, dim) && 0 < $elem.children().length) {
            // find elements
            $elemInner = $elem.find("[width][height]");
            // if no elements found with width height attributes, grab the first child elem
            if (0 === $elemInner.length) {
              $elemInner = $elem.children(":first");
              // else if multiple inner elements found, grab the first of the matched set
            } else if (1 < $elemInner.length) {
              $elemInner = $elemInner.first();
            }
            getWidthHeight($elemInner, dim);
          }

          // if there is a real width and height,
          // and the dimenisons are equal or greater then the minimum
          // let the placeholder inherit this dimension or choose to size to the parents width
          if (
            wpcaData.minPlacehoderDim.w <= dim.w &&
            wpcaData.minPlacehoderDim.h <= dim.h
          ) {
            var dimParent = {},
              r = dim.h / dim.w,
              styles = { width: dim.w, "min-height": dim.h };

            // get wxh of parent element
            if (
              wpcaData.doPlaceholderParentSizing &&
              getWidthHeight($elem.parent(), dimParent, true)
            ) {
              if (dim.w > dimParent.w) {
                var w;
                if (dimParent.w > 1) {
                  w = dimParent.w;
                } else if (dimParent.w === 0) {
                  w = dim.w;
                }
                styles = { "min-height": w * r + "px" };
              }
            }
            $placeholder.css(styles);
          }

          // if element is marked as backup,
          // remove the previous sibling
          if (backup) {
            $elem.show().prev().hide();
          }

          $elem.after($placeholder);
        }
        // update the data and add the placeholder after the element
        $elem.addClass(cssClassblocked);
        // when not blocking
      } else if (!block) {
        if (backup) {
          $elem.hide().prev().show();
        }
        // remove the placholder
        $("#" + cssIdPlaceholderPrefix + c).remove();
        $elem.removeClass(cssClassblocked);
      }
    }

    // update the elements data
    $elem.setMarkedDataObj(data);
  }

  /**
   * Init the blocking/unbloking of content on the current page
   *
   * @returns void
   */
  function initBlocking() {
    var c,
      elementsMarkedMan = $(elemNameBlock).not(
        "[data-" + dataKeyMarkedAuto + "]"
      ),
      // find all marked elements
      elements = $("[data-" + dataKeyMarked + '="1"]'),
      promises = [];

    // Check for manually wrapped elements with <wpca-block></wpca-block>
    for (c = 0; c < elementsMarkedMan.length; c++) {
      var $elem = $(elementsMarkedMan[c]);
      var $elemInner = $elem.find("[data-" + dataKeyMarkedAuto + "]");
      // properties set in a parent <wpca-block>
      var txt = $elem.data("txt") || "";
      var type = $elem.data("type") || "";
      var placeholder = false === $elem.data("placeholder") ? false : true;
      // mark inner marked elements and let them inherit the txt, type and placeholder property
      // but skip the marking for the <wpca-block> itself
      if (0 < $elemInner.length) {
        $elemInner.setMarkedDataObj({
          txt: txt,
          type: type,
          placeholder: placeholder,
        });
        continue;
      }
      // a manual wrapped element needs at least the cc
      var cc = $elem.data("cc") || "";
      if ("" !== cc) {
        var data = {};
        data["id"] = 0;
        data["cc"] = cc;
        data["txt"] = txt;
        data["type"] = type;
        data["context"] = "body";
        data["blocked"] = 1;
        data["placeholder"] = placeholder;
        $elem.setMarkedDataObj(data, true);
      }
    }

    for (c = 0; c < elements.length; c++) {
      $elem = $(elements[c]);

      var dep = $elem.getMarkedData("dep"),
        cc = $elem.getMarkedData("cc"),
        block = front.doBlockCookies(cc),
        hasDepObj = dep !== null && dep.name !== "",
        hasDepDt = hasDepObj && dep.dt >= 0,
        hasDepRetries = hasDepObj && dep.r > 0,
        timeout = parseInt((hasDepDt && dep.dt) || 200),
        retries = parseInt((hasDepRetries && dep.r) || 3);

      if ($elem.isMarked()) {
        if ($elem.is("script") && hasDepObj && !block) {
          promises.push(
            doFnWhenDependencyReady(
              dep.name,
              _doBlockingLogic,
              [$elem, c, cc, block],
              timeout,
              retries
            )
          );
        } else {
          _doBlockingLogic($elem, c, cc, block);
        }
      }
    }

    return Promise.all(promises);
  }

  /**
   * Init position params for the element
   *
   * These are used during animating the element
   *
   * @param {string} status
   *
   * @returns void
   */
  function initPosParams(status) {
    yPos = elem.barbox.hasClass(cssClassPosTop) ? "top" : "bottom";
    posIn = elem.barbox.innerHeight();

    if ("box" === layout) {
      // for the box centered, the parent must be positioned relative
      if (isCenteredBox) {
        yPos = "top";
        $("body").css("position", "relative");
      }

      var space = parseInt(elem.barbox.data("space"));
      posIn = 0 < space ? posIn + space : posIn;
    }

    if (
      hasWPadminbar &&
      statusShow === status &&
      ("top" === yPos || isCenteredBox)
    ) {
      if (600 < $(window).width()) {
        // calculate the absolute value of posIn
        posIn = Math.abs(elem.wpadminbar.innerHeight() - posIn);
        posOut = elem.wpadminbar.innerHeight();
      } else {
        // fix gab on top of screen
        $("body").css("position", "");

        if (window.pageYOffset <= elem.wpadminbar.innerHeight()) {
          posOut = Math.abs(elem.wpadminbar.innerHeight() - window.pageYOffset);
        } else {
          posOut = 0;
        }
      }
    } else {
      posOut = 0;
    }
  }

  /**
   * Init the element position CSS
   *
   * This will position the element on top or bottom of the client window
   * Also invokes the front.setStateClasses() method to show or hide the element
   * For the centered box layout, thie top bottom process is skipped
   *
   * @param {string} status
   *
   * @returns void
   */
  function initStatus(status) {
    if (null !== status) {
      var css = {};

      setStateClasses(status);

      if (statusShow === status) {
        if (isCenteredBox && 640 < $(window).width()) {
          css = { top: "" };
        } else {
          if ("top" === yPos) {
            css = { top: posOut + "px" };
          }
          if ("bottom" === yPos) {
            css = { bottom: posOut + "px" };
          }
        }
      } else {
        if ("top" === yPos) {
          css = { top: "-" + posIn + "px" };
        }
        if ("bottom" === yPos) {
          css = { bottom: "-" + posIn + "px" };
        }
      }

      elem.barbox.css(css);
    }
  }

  /**
   * Init the button status (show/hide)
   */
  function initButtonStatus() {
    // reset buttons
    if (hasBtnResetConsent()) {
      $("." + cssClassBtnResetConsent).each(function () {
        var btnReset = $(this),
          btnAccept = btnReset.next("." + cssClassBtnAccept);

        btnAccept.addClass(cssClassBtnHasReset);

        if (front.needToReplaceResetByAccept()) {
          btnReset.hideButton(1);
          btnAccept.hideButton(0);
        } else {
          btnReset.hideButton(0);
          btnAccept.hideButton(1);
        }
      });
    }

    // accept buttons
    if (
      hasBtnAccept() &&
      !front.needButtonAccept() &&
      !front.needButtonAcceptForCCPA()
    ) {
      $("." + cssClassBtnAccept)
        .not("." + cssClassBtnHasReset)
        .hideButton(1);
    } else {
      $("." + cssClassBtnAccept)
        .not("." + cssClassBtnHasReset)
        .hideButton(0);
    }

    // decline buttons
    if (
      hasBtnDecline() &&
      !front.needButtonDecline() &&
      !front.needButtonDeclineForCCPA()
    ) {
      $("." + cssClassBtnDecline).hideButton(1);
    } else {
      $("." + cssClassBtnDecline).hideButton(0);
    }

    // CC settings buttons
    if (hasBtnCcSettings()) {
      $("." + cssClassBtnCcSettings).hideButton(0);
    }
  }

  function _initStates(args) {
    // init blocking/unblocking
    initBlocking()
      .catch(function (exc) {
        WPIE.log(exc.message, exc);
      })
      .then(function () {
        cEvents.trigger("init_blocking_ready");
      })
      .finally(function () {
        cEvents.trigger("init_states_before", { args: args });

        // init the status of the buttons
        initButtonStatus();

        // setup the params needed later
        initPosParams(args.states.barbox);

        if (args.animate) {
          // animate the lay-out
          animate(args.states.barbox);
        } else {
          // initialize element position and status
          initStatus(args.states.barbox);
        }

        // set all processing buttons to their default texts
        setProcessingButtonDefault();

        // on 1st visit (no consent given) ,check only the required categories
        // else, if it's not the 1st visit, check client saved cookie categories
        if (front.isFirstVisit()) {
          checkCookieCategoriesRequiredOnly();
        } else {
          checkCookieCategoriesClient();
        }

        // show the cookie category settings box(es)
        elem.ccBox.each(function () {
          $(this).show();
        });

        // hide the popup
        if (states.showingPopup) {
          hidePopup();
        }

        // show the layer
        if (front.doLayer()) {
          showLayer();
        } else {
          hideLayer();
        }
      });
  }

  /**
   * Initialize states of bar/box (layout) and blocking
   */
  function initStates(args) {
    if ("object" !== typeof args) {
      throw new Error("Could not initialize: no valid args found.");
    }
    if (
      "undefined" === typeof args.states ||
      "undefined" === typeof args.states.barbox
    ) {
      throw new Error(
        "Could not initialize the bar/box layout: states.barbox argument not found."
      );
    }
    if ("undefined" === typeof args.animate) {
      args.animate = false;
    }
    args.hasBtn = "object" === typeof args.btn;
    args.timeout = parseInt(timeoutProcessing);

    if (0 < args.timeout) {
      setTimeout(function () {
        _initStates(args);
      }, args.timeout);
    } else {
      _initStates(args);
    }
  }

  function doGTMLogic() {
    setTimeout(function () {
      var categoriesArr = getClientCookieCategoriesFromCookie(),
        categoriesStr = "";

      if (!categoriesArr.length) {
        categoriesArr = wpcaData.requiredCc;
      }

      categoriesStr = categoriesArr.join(",");

      if (categoriesArr.length > 0) {
        var dataVariable = {},
          dataEvent = {};
        // push dataLayer variable: categories comma sepatated string
        dataVariable[wpcaData.gtm.dataLayerVar] = categoriesStr;
        // push dataLayer event: categories underscore sepatated string
        dataEvent["event"] =
          wpcaData.gtm.dataLayerEventPrefix +
          categoriesArr.join("_").replace("-", "_");

        front.getDataLayer().push(dataVariable);
        front.getDataLayer().push(dataEvent);
      }

      if (wpcaData.gtm.enableGCMv2) {
        doGoogleConsentModev2Logic();
      }
    }, gtmTimeout);
  }

  function doGoogleConsentModev2Logic() {
    var categoriesArr = getClientCookieCategoriesFromCookie();

    if (!categoriesArr.length) {
      categoriesArr = wpcaData.requiredCc;
    }

    var getMode = (idx, arr) =>
      arr.length > 0 && arr.includes(idx) ? "granted" : "denied";

    var gcm = {};
    gcm.ad_storage = getMode("advertising", categoriesArr);
    gcm.ad_user_data = getMode("advertising", categoriesArr);
    gcm.ad_personalization = getMode("advertising", categoriesArr);
    gcm.analytics_storage = getMode("analytical", categoriesArr);
    gcm.functionality_storage = getMode("functional", categoriesArr);
    gcm.personalization_storage = getMode("functional", categoriesArr);
    gcm.security_storage = getMode("functional", categoriesArr);

    // Update consent
    front.gtag("consent", "update", gcm);
    // Update current concent in local storage
    if (window.localStorage) {
      localStorage.setItem(wpcaData.gtm.localStorageKey, JSON.stringify(gcm));
    }
    // Send event notifying consent has been updated
    front.gtag("event", "wpca_consent_update");
  }

  /**
   * Do 'Accept' logic after client triggered an accept event
   *
   * - Change btn txt with processing notice
   *
   * @param {Object} btn
   * @param {Object[]} categories
   *
   * @returns {boolean|void}
   */
  function doAcceptLogic(btn, categories) {
    if (states.accepting) {
      return;
    } else if (front.acceptedAll()) {
      // fire event: wpca.consent:after
      cEvents.trigger("consent_after", { categories: categories, btn: btn });
      return;
    }

    // flag accepting is active
    states.accepting = true;

    setKookie(wpcaData.cookieNameConsent, "1");

    if ("undefined" === typeof categories) {
      categories = wpcaData.allowedCc;
    }

    if (Array.isArray(categories) && categories.length) {
      // uncomment this will set the wpca_cc cookie with the passed cookies
      // update params that reflect the client cookies
      updateClientCookieAndParams(categories);
    }

    // flag accepting is not active
    states.accepting = false;
    // fire event: wpca.consent:after
    cEvents.trigger("consent_after", { categories: categories, btn: btn });
  }

  /**
   * Do 'Decline' logic after client triggered a decline event
   *
   * - Change btn txt with processing notice
   *
   * @param {Object} btn
   * @param {Object} categories
   *
   * @returns {boolean|void}
   */
  function doDeclineLogic(btn, categories) {
    if (states.declining) {
      return;
    } else if (front.declined()) {
      // fire event: wpca.decline:after
      cEvents.trigger("decline_after", { categories: categories, btn: btn });
      return;
    }

    // flag declining is active
    states.declining = true;

    setKookie(wpcaData.cookieNameConsent, "0");

    if ("undefined" === typeof categories) {
      categories = wpcaData.requiredCc;
    }

    if (Array.isArray(categories) && categories.length) {
      // uncomment this will set the wpca_cc cookie with the passed cookies
      // update params that reflect the client cookies
      updateClientCookieAndParams(categories);
    }

    checkCookieCategoriesRequiredOnly();

    // flag declining is not active
    states.declining = false;
    // fire event: wpca.decline:after
    cEvents.trigger("decline_after", { categories: categories, btn: btn });
  }

  /**
   * Do 'reset consent' logic after client triggered a reset consent event
   *
   * - Change btn txt with processing notice
   *
   * @param {Object} btn
   *
   * @returns {boolean|void}
   */
  function doResetConsentLogic(btn) {
    if (states.resetting) {
      return;
    }

    // flag resetting is active
    states.resetting = true;

    // delete the consent and cc cookies
    deleteKookie(wpcaData.cookieNameConsent);

    // update params that reflect the client cookies
    updateClientCookieAndParams([]);

    // check all cc checkboxes
    checkCookieCategoriesAllowed();

    // flag resetting is not active
    states.resetting = false;
    // fire event: wpca.reset:after
    cEvents.trigger("reset_after", { btn: btn });
  }

  /**
   * Do Cookie Category 'save settings' logic
   *
   * - Change btn txt with processing notice
   *
   * @param {Object} btn
   * @param {Object[]} categories
   *
   * @returns {boolean|void}
   */
  function doCcSaveSettLogic(btn, categories) {
    // if already saved and nothing has changed, return false
    if (states.savingcc || !hasChangedCat) {
      return;
    }

    // flag saving cc is active
    states.savingcc = true;

    if (Array.isArray(categories) && categories.length) {
      // uncomment this will set the wpca_cc cookie with the passed cookies
      // update params that reflect the client cookies
      updateClientCookieAndParams(categories);
    }

    // flag saving cc is not active
    states.savingcc = false;
    // fire event: wpca.cc:saved
    cEvents.trigger("cc_saved", { categories: categories });
  }

  /**
   * Initialize the 3rd party compatibiliy logic
   */
  function doInit3rdPartyContentLogic() {
    if (wpcaData.init3rdPartyItems && wpcaData.init3rdPartyItems.length > -1) {
      setTimeout(function () {
        try {
          for (var i = 0; i < wpcaData.init3rdPartyItems.length; i++) {
            var item = wpcaData.init3rdPartyItems[i];

            WPIE.checkForRequiredProps(
              init3rdPartyItemProps,
              item,
              true,
              "3rdPartyItem"
            );

            cEvents.trigger("tp_init_before", item);

            switch (item.type) {
              case "avada-fusion-gmaps":
                front.initFusionGmaps(item);
                break;
              case "divi-gmaps":
                front.initDiviGmaps(item);
                break;
              default:
                front.initRegistered3rdParty(item);
                break;
            }

            cEvents.trigger("tp_init_after", item);
          }
        } catch (exc) {
          WPIE.log(exc.message, exc);
        }

        // wait to be (almost) sure the context is ready
        // increase this when receiving errors
      }, init3rdPartyTimeout);
    }
  }

  /**
   * Wrapper for adding scroll events
   *
   * @param {function} callback
   * @param {Object} params
   *
   * @returns void
   */
  function addScrollEventListener(callback, params) {
    // use JavaScript native addEventListener because jQuery scroll()
    // takes a #my-hash also as a scroll event
    if (window.addEventListener) {
      // scrollbar
      window.addEventListener(
        "scroll",
        function (e) {
          var _e = window.event || e;
          callback(_e, params);
        },
        false
      );

      // Chrome, IE, opera
      window.addEventListener(
        "mousewheel",
        function (e) {
          var _e = window.event || e;
          callback(_e, params);
        },
        false
      );

      // Firefox
      window.addEventListener(
        "DOMMouseScroll",
        function (e) {
          var _e = window.event || e;
          callback(_e, params);
        },
        false
      );

      window.addEventListener(
        "touchmove",
        function (e) {
          var _e = window.event || e;
          callback(_e, params);
        },
        false
      );
    } else {
      // IE 8 and older
      window.attachEvent("onmousewheel", function (e) {
        var _e = window.event || e;
        callback(_e, params);
      });
    }
  }

  /**
   * Event callback for wpca.initblocking:ready event
   *
   * @param {Object} e
   */
  function handlerBlockingReady(e) {
    // (re) initialize content for 3rd parties after consent or on pageload
    doInit3rdPartyContentLogic();
  }

  /**
   * Click event callback for wpca.consent:after event
   *
   * @param {Object} e
   */
  function handlerConsentAfter(e, args) {
    if ("undefined" === typeof args.btn) {
      args.btn = false;
    }

    // force a page reload if needed
    if (front.needPageReloadAfterConsent()) {
      window.location.reload(true);
    }

    if (wpcaData.gtm.optimize) {
      doGTMLogic();
    }

    // check all cc checkboxes if all cookie categories are accepted
    if (front.acceptedAll()) {
      checkCookieCategoriesAllowed();
    }

    states.barbox = statusHide;
    initStates({
      states: states,
      btn: args.btn,
      animate: true,
    });
  }

  /**
   * Click event callback for wpca.decline:after event
   *
   * @param {Object} e
   */
  function handlerDeclineAfter(e, args) {
    if ("undefined" === typeof args.btn) {
      args.btn = false;
    }

    if (wpcaData.gtm.optimize) {
      doGTMLogic();
    }

    states.barbox = statusHide;
    initStates({
      states: states,
      btn: args.btn,
      animate: true,
    });
  }

  /**
   * Click event callback for wpca.reset:after event
   *
   * @param {Object} e
   */
  function handlerResetAfter(e, args) {
    if ("undefined" === typeof args.btn) {
      args.btn = false;
    }

    if (wpcaData.gtm.optimize) {
      doGTMLogic();
    }

    states.barbox = statusShow;
    initStates({
      states: states,
      btn: args.btn,
      animate: true,
    });

    // unbind events and bind again
    events.unbind().bind();
  }

  /**
   * Click event callback for processing buttons
   *
   * @param {Object} e
   */
  function handlerProcessingButtonClick(e) {
    try {
      e.preventDefault();

      var btn = $(this);
      btn.processingBntState("process");
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for accept button
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerConsentClickAccept(e) {
    try {
      e.preventDefault();

      // Maybe a cc is passed through the accept button
      var cc = $(this).data("cc"),
        ccArray = [cc];

      // if the passed category is allowed, use it
      if (isAllowedCookieCategory(cc)) {
        // But check if the category doesn't already exist
        if (!front.hasClientThisCookieCategory(cc)) {
          ccArray = clientCc.concat(ccArray);
        } else {
          ccArray = clientCc;
        }

        // ensure required categories are passed
        addRequiredCookieCategories(ccArray);

        doAcceptLogic($(this), ccArray);
      } else {
        //TODO consider to pass 2th param categories=wpcaData.allowedCc
        doAcceptLogic($(this));
      }

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for decline button
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerConsentClickDecline(e) {
    try {
      e.preventDefault();

      // TODO consider to pass (1st btn=false) and 2th param categories=[]
      doDeclineLogic($(this));

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for reset consent button
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerConsentClickResetConsent(e) {
    try {
      e.preventDefault();

      doResetConsentLogic($(this));

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for settings button
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerCcClickOpenSett(e) {
    try {
      e.preventDefault();

      showPopup();

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback when clicked in the current window
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerConsentClickWindow(e) {
    try {
      if (front.doLayer()) {
        return false;
      }

      if (window === e.currentTarget && "click" === e.type) {
        var target = e.target;

        // check for hyperlinks wrapped by an image tag
        if ("IMG" === target.nodeName && "A" === target.parentNode.nodeName) {
          target = target.parentNode;
        }
        if ("A" === target.nodeName) {
          // stop here if consent or decline already given,
          // otherwise a clicked link will not work
          if (front.accepted() || front.declined()) {
            return;
          }
          if (!$(target).hasClass(wpcaData.policyCssClass)) {
            e.preventDefault();

            //TODO consider to pass (1st btn=false) and 2th param categories=wpcaData.allowedCc
            doAcceptLogic(false);

            // redirect to the clicked link target
            window.location = target.href;
          }
        }
      }
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Scroll event callback
   *
   * @param {Object} e
   * @param {Object} params
   *
   * @returns {boolean} false
   */
  function handlerConsentscrollWindow(e, params) {
    try {
      if (
        -1 !== window.location.search.indexOf(wpcaData.queryVarBypass + "=1")
      ) {
        return false;
      }

      var scrollTop;

      // stop here if consent or decline already given
      if (front.accepted() || front.declined()) {
        return false;
      }

      scrollTop = $(window).scrollTop();

      if (minScrollTop > scrollTop) {
        return false;
      }

      if (front.doLayer()) {
        return false;
      }

      //TODO consider to pass 2nd param categories=wpcaData.allowedCc
      doAcceptLogic(false);
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click Close X event callback for the bar or box
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerClickCloseBarBox(e) {
    try {
      var accepting = !useXAsDismiss;

      // TODO consider to pass 2nd param as
      // categories=wpcaData.allowedCc for doAcceptLogic()
      // categories=[] for doDeclineLogic()
      if (accepting) {
        doAcceptLogic(false);
      } else {
        doDeclineLogic(false);
      }

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click Close X event callback for the popup
   *
   * Only toggle the popup when the layer or closing x is clicked
   *
   * @param {Object} e
   *
   * @returns {boolean} false
   */
  function handlerClickClosePopup(e) {
    try {
      var clickedLayer = idLayer === e.target.id,
        clickedCloseX =
          -1 !==
          (
            e.target.className.baseVal ||
            e.target.parentElement.className.baseVal
          ).indexOf(cssClassIconCloseX);

      if (clickedLayer || clickedCloseX) {
        hidePopup();
      }

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for Cookie Category setting inputs
   *
   * @param {Object} e
   *
   * @returns void
   */
  function handlerCcClickInput(e) {
    try {
      var cb = $(this),
        elCcBox = cb.parents("." + cssClassCcBox),
        elCcBoxId = elCcBox.data(dataKeyCcBoxId),
        cssId = cssClassCcBox + "-" + elCcBoxId,
        cat = cb.data(dataKeyCcCat),
        checked = cb.is(":checked");

      // sync Cookie Category setting boxes
      elem.ccBox.not("#" + cssId).each(function () {
        $(this).cbCheckForCategory(checked, cat);
      });
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Click event callback for Cookie Categories save settings button
   *
   * @param {Object} e
   *
   * @returns {boolean|void}
   */
  function handlerCcClickSaveSett(e) {
    try {
      e.preventDefault();

      var btn = $(this),
        // get the checkbox inputs
        inputs = getInputsForBtn(btn),
        // get the selected categories based on the inputs
        categories = getSelectedCookieCategories(inputs);

      hasChangedCat = hasChangedCookieCategories(categories, clientCc);

      // return now if nothing changed
      if (!hasChangedCat) {
        setProcessingButtonDefault();
        hidePopup();
        return;
      }

      // check if required categories are present
      if (!hasRequiredCookieCategory(categories)) {
        categories = wpcaData.requiredCc;
        WPIE.alert(
          "not all required categories found. Required: " +
            wpcaData.requiredCc.join()
        );
      }

      // do accept logic if needed by hooking into wpca.cc:saved event
      // than, hide the popup if open with a short delay for usability
      $(window).one(cEvents.names.cc_saved, function (e, args) {
        doAcceptLogic(btn, args.categories);
      });

      // do save settings logic
      doCcSaveSettLogic(btn, categories);

      return false;
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  }

  /**
   * Handler for scrolling the window
   *
   * Takes care of the WP adminbar if present
   * WP currently makes the adminbar position:absolute from width <= 600
   *
   * @param {Object} e
   *
   * @returns void
   */
  function handlerScrollWindow(e) {
    if (
      hasWPadminbar &&
      "top" === yPos &&
      statusShow === states.barbox &&
      600 >= $(window).width()
    ) {
      var y = null;

      if (window.pageYOffset < elem.wpadminbar.innerHeight()) {
        y = elem.wpadminbar.innerHeight() - window.pageYOffset;
      } else if (elem.wpadminbar.innerHeight() <= window.pageYOffset) {
        y = 0;
      }

      if (null !== y) elem.barbox.css("top", y + "px");
    }
  }

  /**
   * Handler for the keyup event in the document
   *
   * Hide the popup when the ESC key is triggered
   *
   * @param {Object} e
   *
   * @returns void
   */
  function handlerDocumentKeyup(e) {
    if (27 === e.keyCode && states.showingPopup) {
      hidePopup();
    }
  }

  front.getDataLayer = function () {
    return window[wpcaData.gtm.dataLayerName] &&
      Array.isArray(window[wpcaData.gtm.dataLayerName])
      ? window[wpcaData.gtm.dataLayerName]
      : [];
  };

  /**
   * Add arguments to the Google dataLayer
   */
  front.gtag = function () {
    this.getDataLayer().push(arguments);
  };

  /**
   * Window onload event callback
   *
   * @param {Object} e
   */
  front.handlerOnload = function (e) {
    setTimeout(function () {
      try {
        if (wpcaData.gtm.optimize) {
          doGTMLogic();
        }
      } catch (exc) {
        WPIE.log(exc.message, exc);
      } finally {
        window.removeEventListener("load", front.handlerOnload, false);
      }
    }, 0);
  };

  /**
   * Determine if client has accepted all cookie categories
   *
   * @returns {bool}
   */
  front.acceptedAll = function () {
    if (this.accepted() && this.hasClientCookieCategoriesAll()) {
      return true;
    } else if (this.accepted() && !hasClientCc) {
      return true;
    } else {
      return false;
    }
  };

  /**
   * Determine if user has given consent
   *
   * @returns {bool}
   */
  front.accepted = function () {
    return "1" === readKookie(wpcaData.cookieNameConsent);
  };

  /**
   * Determine if user has declined
   *
   * @returns {bool}
   */
  front.declined = function () {
    return "0" === readKookie(wpcaData.cookieNameConsent);
  };

  /**
   * Determine if user has a 1st visit
   *
   * @returns {bool}
   */
  front.isFirstVisit = function () {
    return !this.accepted() && !this.declined();
  };

  /**
   * Determine if are bypassing consent
   *
   * @returns {bool}
   */
  front.isBypassingConsent = function () {
    return wpcaData.bypassingConsent;
  };

  /**
   * Determine if needing the accept button
   *
   * Only need the accept button when:
   * - not accepted all categories yet
   * - has the right consent method configured
   *
   * @returns {bool}
   */
  front.needButtonAccept = function () {
    return !this.acceptedAll() && hasConsentMethodForBtnAccept();
  };

  /**
   * Determine if needing the accept (I understand) button when CCPA is active
   *
   * Only need the accept button when:
   * - CCPA is active
   * - CCPA settings are configured to show the accept button
   * - cookies (all categories) are not accept yet
   *
   * @returns {bool}
   */
  front.needButtonAcceptForCCPA = function () {
    return hasCCPA && wpcaData.ccpa.showBtnAccept && !this.acceptedAll();
  };

  /**
   * Determine if needing the decline button
   *
   * Only need the decline button when:
   * - not already declined AND
   * - not already accepted AND
   * - 3rd party cookies are not set before consent
   *
   * @returns {bool}
   */
  front.needButtonDecline = function () {
    return (
      !this.declined() && !this.accepted() && !do3rdPartyCookiesBeforeConsent()
    );
  };

  /**
   * Determine if needing the decline (opt-out) button when CCPA is active
   *
   * - CCPA is active
   * - CCPA settings are configured to show the decline button
   * - cookies are not declined yet
   *
   * @returns {bool}
   */
  front.needButtonDeclineForCCPA = function () {
    return hasCCPA && wpcaData.ccpa.showBtnDecline && !this.declined();
  };

  /**
   * Determine if needing to replace the reset button by an Accept button
   *
   * @returns {bool}
   */
  front.needToReplaceResetByAccept = function () {
    return !this.accepted() && wpcaData.replaceResetBtn;
  };

  /**
   * Determine if needing a page reload afer consent
   *
   * @returns {bool}
   */
  front.needPageReloadAfterConsent = function () {
    return wpcaData.reloadAfterConsent;
  };

  /**
   * Determine if needing the consent layout
   *
   * Only need the layout when:
   * - is NOT bypassing consent OR
   * - did NOT declined OR
   * - is a 1st visit
   *
   * @returns {bool}
   */
  front.needConsentLayout = function () {
    if (this.isBypassingConsent()) {
      return false;
    } else if (this.declined()) {
      return false;
    } else if (this.isFirstVisit()) {
      return true;
    } else {
      return false;
    }
  };

  /**
   * Determine if needing a layer
   *
   * @returns {bool}
   *
   * TODO test hasConsentMethodForLayer()
   */
  front.doLayer = function () {
    if (
      "" !== wpcaData.cookiePolicyPathRel &&
      wpcaData.cookiePolicyPathRel === window.location.pathname
    ) {
      return false;
    } else if (
      hasCCPA &&
      "" !== wpcaData.ccpa.dnsmpiUrlRel &&
      wpcaData.ccpa.dnsmpiUrlRel === window.location.pathname
    ) {
      return false;
    } else if (!front.needConsentLayout()) {
      return false;
    } else if (!hasConsentMethodForLayer()) {
      return false;
    } else {
      return wpcaData.showLayer && hasLayer;
    }
  };

  /**
   * Get the declined cookie categories
   *
   * @returns {Object[]} the categories
   */
  front.getClientCookieCategoriesDeclined = function () {
    return WPIE.arrDiff(wpcaData.allowedCc, clientCc);
  };

  /**
   * Determine if client has all allowed categories
   *
   * @returns {bool}
   */
  front.hasClientCookieCategoriesAll = function () {
    if (hasClientCc) {
      var declined = this.getClientCookieCategoriesDeclined();

      return 0 === declined.length;
    } else {
      return false;
    }
  };

  /**
   * Determine if client has given category
   *
   * @returns {bool}
   */
  front.hasClientThisCookieCategory = function (cc) {
    return hasClientCc && -1 !== clientCc.indexOf(cc);
  };

  /**
   * Determine if the bar or box is visible
   *
   * @returns {boolean}
   */
  front.hasLayout = function () {
    return elem.barbox.hasClass(classShow);
  };

  /**
   * Perform a reset consent
   */
  front.triggerResetConsent = function () {
    try {
      // omit the reset button
      doResetConsentLogic();
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  };

  /**
   * Determine if cookies should be blocked
   *
   * @returns {bool}
   */
  front.doBlockCookies = function (cc) {
    var cc = "undefined" !== typeof cc ? cc.trim() : "";

    var clientHasAll = this.hasClientCookieCategoriesAll(),
      checkForClientCc = false,
      clientHasCategory,
      // block cookies by default
      block = true;

    // Does the client has the passed category set
    if ("" !== cc && isAllowedCookieCategory(cc)) {
      checkForClientCc = true;
      clientHasCategory = this.hasClientThisCookieCategory(cc);
    }

    if (checkForClientCc && clientHasCategory) {
      block = false;

      // and don't block cookies if
      // client has accepted ALL categories
    } else if (this.accepted() && clientHasAll) {
      block = false;

      // and don't block cookies if
      // client has accepted and hasn't got categories yet
      // This is the case when the user accepts without saving the settings
    } else if (this.accepted() && !hasClientCc) {
      block = false;

      // and don't block cookies if
      // client has NOT accepted and declined yet (1st visit untill client makes a choice)
      // and ALL (3rd party) cookies are allowed before consent
    } else if (this.isFirstVisit() && do3rdPartyCookiesBeforeConsent()) {
      block = false;
    }

    return block;
  };

  /**
   * Get the name of an event by index
   *
   * @param {String} idx
   *
   * @returns {String}
   */
  front.getEventName = function (idx) {
    return cEvents.getName(idx);
  };

  front.initContentForCc = function (item, func, args) {
    if (isAllowedCookieCategory(item.cc) && !this.doBlockCookies(item.cc)) {
      try {
        func = "string" === typeof func ? func : item.jsFunc;

        if (!item.depName) {
          item.depName = func;
        }

        doFnWhenDependencyReady(
          item.depName,
          func,
          args,
          parseInt(item.depDt || 200),
          parseInt(item.depRetries || 3)
        ).catch(function (err) {
          WPIE.log(err);
        });
      } catch (exc) {
        WPIE.log(exc.message, exc);
      }
    }
  };

  front.initRegistered3rdParty = function (item) {
    this.initContentForCc(item);
  };

  front.initFusionGmaps = function (item) {
    var maps = $(".fusion-google-map");

    // loop over all fusion maps and
    // call the fusion_run_map_{ID} function
    maps.each(function () {
      var fn = "fusion_run_map_" + $(this).attr("id");
      item.jsFunc = fn;
      front.initContentForCc(item);
    });
  };

  front.initDiviGmaps = function (item) {
    var maps = $(".et_pb_map_container");

    // loop over all divi maps and
    // call the et_pb_map_init function
    maps.each(function () {
      var fn = "et_pb_map_init";
      item.jsFunc = fn;
      front.initContentForCc(item, null, $(this));
    });
  };

  front.doLayoutHTMLRequest = function () {
    var payload = {},
      data = {},
      forceRest = false;

    loadingHtml = true;

    if (-1 !== window.location.search.indexOf("wpca-fm=rest")) {
      forceRest = true;
    }

    if (wpcaData.fetchMethod === "rest" || forceRest) {
      if (forceRest) {
        data["wpca-do-rest"] = "1";
      }
      data["_wpnonce"] = wpcaData.restNonce;
      var restUrl =
        wpcaData.restUrl + wpcaData.ns + "/v1/" + restActionRenderLayout;
      layoutRequest = $.get(restUrl, data, "html");
    } else {
      data["context"] = wpcaData.ajaxContextFrontend;
      payload["action"] = "wpie-action";
      payload[wpcaData.ns + "_action"] = ajaxActionRenderLayout;
      payload["data"] = data;
      payload["nonce"] = wpcaData.nonce;

      layoutRequest = $.get(wpcaData.ajaxurl, payload, "html");
    }
  };

  front.loadLayout = function () {
    elem.wrapper = $("#" + idWrapper);

    if (elem.wrapper.length) {
      return layoutRequest
        .done(function (html) {
          elem.wrapper.html(html);
          return true;
        })
        .fail(function (xhr) {
          if (xhr.responseJSON && xhr.responseJSON.message) {
            WPIE.log(xhr.responseJSON.message);
          }
        });
    } else {
      throw new Error("missing wrapper element");
    }
  };

  /**
   * Init the element logic when the DOM is ready
   *
   * Tasks:
   *      - validating required global params
   *      - declaring params
   *      - callings methods: initStates(), events.bind()
   *
   * @returns void
   */
  front.init = function () {
    try {
      if (this.done) {
        return;
      }

      if (!wpcaData.loadHtmlEarly && !loadingHtml) {
        front.doLayoutHTMLRequest();
      }

      // load the bar/box layout
      this.loadLayout().done(function () {
        // bar of box?
        layout = wpcaData.layout;
        // init DOM elements
        elem.barbox = $("#wpca-" + layout);
        elem.wpadminbar = $("#wpadminbar");
        elem.privacyBtns = $("." + wpcaData.policyCssClass);
        elem.privacyBtn = elem.barbox.find("." + wpcaData.policyCssClass);
        elem.ccBox = $("." + cssClassCcBox);
        elem.layer = $("#" + idLayer);
        elem.popup = $("#" + cssIdPopupModal);
        elem.closeBarBox = elem.barbox.find("." + cssClassIconCloseX);
        elem.closePopup = elem.popup.find("." + cssClassIconCloseX);
        elem.reconsiderIcon = $("#" + cssIdIconReconsider);
        // configs passed by wpcaData
        useXAsDismiss = wpcaData.useXAsDismiss;
        consentMethod = parseInt(wpcaData.consentMethod);
        cookieExpire = wpcaData.cookieExpire
          ? wpcaData.cookieExpire
          : cookieExpire;
        minScrollTop =
          0 <= wpcaData.minScrollTop ? wpcaData.minScrollTop : minScrollTop;
        // client cookies
        clientCc = getClientCookieCategoriesFromCookie();
        // flags
        isCenteredBox = elem.barbox.hasClass(cssClassCenteredBox);
        hasWPadminbar = null != elem.wpadminbar[0] ? true : false;
        hasLayer = 0 < elem.layer.length;
        hasPopup = 0 < elem.popup.length;
        hasCloseEl = wpcaData.hasClose;
        hasClientCc = 0 < clientCc.length;
        hasCloseElBarBox = hasCloseEl && 0 < elem.closeBarBox.length;
        hasReconsiderIcon = 0 < elem.reconsiderIcon.length;
        animateDuration =
          0 <= wpcaData.animateDuration
            ? wpcaData.animateDuration
            : animateDuration;

        // set if CCPA is active: current request is from california
        // the wpcaData.ccpa object should contain:
        // consentAfterUse showBtnAccept showBtnDecline dnsmpiCssClass dnsmpiUrlRel
        hasCCPA = null != wpcaData.ccpa;

        // the timeout used for reloading/initializing content
        init3rdPartyTimeout =
          wpcaData.init3rdPartyTimeout || init3rdPartyTimeout;

        // extra check. if client hasn't accepted and declined yet (1st visit), but Cookie Category cookie is present
        // delete the Cookie Category cookie
        if (front.isFirstVisit() && clientCc.length) {
          deleteKookie(wpcaData.cookieNameCc);
        }

        // set the status of the element (show/hide) after the cookie has been read
        states.barbox = !front.needConsentLayout() ? statusHide : statusShow;

        // extend the jQuery with methods stored in param "extend"
        $.fn.extend(extend);

        // init all layout params (bar/box/buttons and blocking logic
        initStates({
          states: states,
        });

        // bind events
        events.bind();

        // start observing
        obzerver.init().obzerve();

        front.done = true;

        cEvents.trigger("init_ready");
      });
    } catch (exc) {
      WPIE.log(exc.message, exc);
    }
  };

  return front;
})(jQuery || {}, window.WPCAFR || {});

(function ($) {
  try {
    WPIE.checkForRequiredProps(
      [
        "loadHtmlEarly",
        "fetchMethod",
        "layout",
        "policyCssClass",
        "cookiePolicyPathRel",
        "queryVarBypass",
        "cookieNameConsent",
        "cookieNameCc",
        "cookiesBeforeConsent",
        "consentMethod",
        "showLayer",
        "hasClose",
        "useXAsDismiss",
        "minScrollTop",
        "bypassingConsent",
        "ssl",
        "allowedCc",
        "requiredCc",
        "replaceResetBtn",
        "doPlaceholder",
        "doPlaceholderParentSizing",
        "blockedAssetsUri",
        "reloadAfterConsent",
        "minPlacehoderDim",
        "animateDuration",
        "gtm",
        "init3rdPartyTimeout",
        "init3rdPartyItems",
        "ccLabels",
      ],
      wpcaData,
      false,
      "wpcaData"
    );

    if (wpcaData.loadHtmlEarly) {
      WPCAFR.doLayoutHTMLRequest();
    }

    $(function ($) {
      // call init method when DOM is ready
      WPCAFR.init();
    });
    // There seems to be an issue since jQuery 3 with a window load event
    // set from within the document ready event. Therefor call the
    // `handlerOnload()` from outside the ready event handler
    window.addEventListener("load", WPCAFR.handlerOnload, false);
  } catch (exc) {
    WPIE.log(exc.message, exc);
  }
})(jQuery || {});
