﻿class StickyConstants {
    static CSS_CLASS =

{
    component: "stickyManager", sticky: "stickyManager__sticky", hidden: "stickyManager__sticky-hidden", scroll: "stickyManager__sticky-scrolling-", scrollUp: "stickyManager__sticky-scrolling-scrollUp", visible: "stickyManager__sticky-visible", forceOverflow: "stickyManager__forceOverflow", appearsAfterOverflow: "stickyManager--appears-after-overflow", parentHidden: "stickyManager__parent-hidden"
}

;

static DATA_ATTRIBUTE = {
    dataType: "stickyType", dataClass: "stickyClass", dataContainer: "stickyContainer", dataPosition: 'stickyPosition',
}

;

static STICKY_TYPES = {
    always: "always", scrollUp: "scrollUp", scrollDown: "scrollDown", none: "none"
}

;

static STICKY_POSITION = {
    TOP: 'top', BOTTOM: 'bottom'
}

;
}

class StickyElement {
    constructor($element)

{
    this .$element = $element;
    this ._updateAppearsAfterOverflowStatus();
}

destroy() {
    this .$element.classList.remove( StickyConstants.CSS_CLASS.component, StickyConstants.CSS_CLASS.sticky, StickyConstants.CSS_CLASS.visible );
    this .$element.style.top = null;
    this .$element.style.zIndex = null;
}

getHeight($element = null) {
    if(!$element)

{
    $element = this.$element;
}

return Math.floor($element.getBoundingClientRect().height);
}

_updateAppearsAfterOverflowStatus() {
    if(this.$element.classList.contains(StickyConstants.CSS_CLASS.appearsAfterOverflow))

{
    this .$element.parentNode.classList.add(StickyConstants.CSS_CLASS.parentHidden);
}

}

get dataType() {
    return this.$element.dataset[StickyConstants.DATA_ATTRIBUTE.dataType] || StickyConstants.STICKY_TYPES.always;
}

get parentContainer() {
    return this.$element.closest(this.$element.dataset[StickyConstants.DATA_ATTRIBUTE.dataContainer]) || null;
}

get stickyPosition() {
    return this.$element.dataset.stickyPosition || StickyConstants.STICKY_POSITION.TOP;
}

isParentInViewport() {
    if (!this.parentContainer) return false;
    const rect = this.parentContainer.getBoundingClientRect();
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
    return rect.bottom > 0 && rect.top < viewportHeight;
}

isSticky() {
    return this.$element.classList.contains(StickyConstants.CSS_CLASS.sticky);
}

_setSticky() {
    this ._createPlaceholder();
    this .$element.classList.add(StickyConstants.CSS_CLASS.sticky);
}

_hideSticky() {
    if(this.isSticky())

{
    this .$element.style.top = `-$

{
    this .getHeight()
}

px`;
this.$element.classList.remove(StickyConstants.CSS_CLASS.visible);
}
}

_showStickyTop(top) {
    this .$element.classList.add(StickyConstants.CSS_CLASS.visible);
    this .$element.style.top = `$

{
    top
}

px`;
this.$element.style.bottom = "";
}

_showStickyBottom() {
    this .$element.classList.add(StickyConstants.CSS_CLASS.visible);
    this .$element.style.top = "";
    this .$element.style.bottom = "24px";
}

_unsetSticky() {
    this ._removePlaceholder();
    this .$element.classList.remove(StickyConstants.CSS_CLASS.sticky, StickyConstants.CSS_CLASS.visible);
    this .$element.style.top = "";
    this .$element.style.bottom = "";
}

_hasReachedViewportTop() {
    if (this.$element.classList.contains(StickyConstants.CSS_CLASS.forceOverflow))

{
    return this._hasExitedViewportBottom(this.$element);
}

const rect = this.$element.getBoundingClientRect();
return rect.top < 0;
}

_hasExitedViewportBottom() {
    const rect = this.$element.getBoundingClientRect();
    return rect.bottom < 0;
}

_isOverflowing(top, isScrollingDown = false) {
    let offset = this.$element.parentNode.offsetTop;
    const prevSibling = this.$element.previousElementSibling;
    if (prevSibling)

{
    offset = prevSibling.offsetTop + this.getHeight(prevSibling);
}

if (this.$element.classList.contains(StickyConstants.CSS_CLASS.forceOverflow) && isScrollingDown) {
    offset += this.getHeight();
}

return window.scrollY > offset - top;
}

_shouldBeVisible(eventType) {
    const expectedType = this._getTypeByViewport(this.dataType);
    return expectedType === eventType || expectedType === StickyConstants.STICKY_TYPES.always;
}

_createPlaceholder() {
    if (!this.$element.parentNode.getElementsByClassName(StickyConstants.CSS_CLASS.hidden).length)

{
    const $placeholder = document.createElement("div");
    $placeholder.classList.add(StickyConstants.CSS_CLASS.hidden);
    $placeholder.style.paddingTop = `$

{
    this .getHeight()
}

px`;
this.$element.after($placeholder);
}
}

_removePlaceholder() {
    const placeholders = this.$element.parentNode.getElementsByClassName(StickyConstants.CSS_CLASS.hidden);
    Array .from(placeholders).forEach(el => el.remove());
}

_getTypeByViewport() {
    const arrType = this.dataType.split(",");
    let output = arrType[0];
    if (CommonUtils.viewports.isXL() && arrType.length > 3)

{
    output = arrType[3];
}

else if ((CommonUtils.viewports.isXL() || CommonUtils.viewports.isL()) && arrType.length > 2) {
    output = arrType[2];
}

else if ((CommonUtils.viewports.isXL() || CommonUtils.viewports.isL() || CommonUtils.viewports.isM()) && arrType.length > 1) {
    output = arrType[1];
}

return output;
}
}

class ScrollHandler {
    constructor(callback)

{
    this .SCROLL_OFFSET = 5;
    this .lastScrollTop = 0;
    this .ignoreScroll = false;
    this .callback = callback;
    this ._direction = 'none';
    this .addEventListeners();
}

addEventListeners() {
    window .addEventListener("scroll", () => this.handleScroll(), false);
    window .addEventListener("resize", () => this.callback());
}

handleScroll() {
    if (this.ignoreScroll)

{
    this .ignoreScroll = false;
    return;
}

const st = window.scrollY || document.documentElement.scrollTop;
if (st > this.SCROLL_OFFSET && Math.abs(st - this.lastScrollTop) <= this.SCROLL_OFFSET) {
    return;
}

this._direction = st > this.lastScrollTop
? StickyConstants.STICKY_TYPES.scrollDown
: StickyConstants.STICKY_TYPES.scrollUp;

this.callback(this._direction);
this.lastScrollTop = Math.max(st, 0);

this._updateBodyScrollClass();
}

isScrollingDown() {
    return this._direction === StickyConstants.STICKY_TYPES.scrollDown;
}

setIgnoreScroll() {
    this .ignoreScroll = true;
}

get direction() {
    return this._direction;
}

_updateBodyScrollClass() {
    Object .values(StickyConstants.STICKY_TYPES).forEach(type => {
            document.body.classList.remove(StickyConstants.CSS_CLASS.scroll + type);
        });
    document .body.classList.add(StickyConstants.CSS_CLASS.scroll + this._direction);
}

}

class StickyManager {
    constructor()

{
    this .elements = [];
    this ._top = 0;
    this .init();
}

get CSS_CLASS () {
    return StickyConstants.CSS_CLASS;
}

get DATA_ATTRIBUTE () {
    return StickyConstants.DATA_ATTRIBUTE;
}

top() {
    return this._top;
}

init() {
    this .elements = [];
    const $elements = document.getElementsByClassName(StickyConstants.CSS_CLASS.component);
    if ($elements.length)

{
    this .collectStickyElementsAndInit($elements);
}

}

initElement($element, isOverFlow = false, isSticky = false, isStickyVisible = false, dataType = []) {
    const classesToAdd = [StickyConstants.CSS_CLASS.component];
    if (isOverFlow)

{
    classesToAdd .push(StickyConstants.CSS_CLASS.forceOverflow);
}

if (isSticky) {
    classesToAdd .push(StickyConstants.CSS_CLASS.sticky);
}

if (isStickyVisible) {
    classesToAdd .push(StickyConstants.CSS_CLASS.visible);
}

if (Array.isArray(dataType) && dataType.length > 0) {
    $element.dataset[StickyConstants.DATA_ATTRIBUTE.dataType] = dataType;
}

$element.classList.add(...classesToAdd);
this.init();
}

destroyElement($element) {
    const obj = this.elements.find(item => item.$element === $element);
    if (obj && typeof obj.destroy === 'function')

{
    obj .destroy();
    this .init();
    return true;
}

return false;
}

collectStickyElementsAndInit($elements) {
    Array .from($elements).forEach(($element, index) => {
            this.elements.push(new StickyElement($element));
            $element.style.zIndex = 100 + $elements.length - index;
        });
    this .scrollHandler = new ScrollHandler(() => this.updateStickyPositions());
    this .updateStickyPositions();
}

setIgnoreScroll() {
    if(this.scrollHandler)

{
    this .scrollHandler.setIgnoreScroll();
}

}

updateStickyPositions() {
    this ._top = 0;
    this .elements.forEach((stickyElement) => {
            this._handleStickyElement(stickyElement);
        });
}

_handleStickyElement(stickyElement) {
    if (this._shouldUnstickElement(stickyElement))

{
    stickyElement ._unsetSticky();
    return;
}

if (this._shouldSetSticky(stickyElement)) {
    stickyElement ._setSticky();
}

else {
    stickyElement ._unsetSticky();
    return;
}

this._updateStickyVisibility(stickyElement);
}

_shouldUnstickElement(stickyElement) {
    const

{
    parentContainer
}

= stickyElement;
return parentContainer && (!stickyElement.isParentInViewport() || !stickyElement._shouldBeVisible(this.scrollHandler.direction));
}

_shouldSetSticky(stickyElement) {
    return stickyElement._hasReachedViewportTop() || stickyElement._isOverflowing(this._top, this.scrollHandler.isScrollingDown());
}

_updateStickyVisibility(stickyElement) {
    if (!stickyElement._shouldBeVisible(this.scrollHandler.direction))

{
    return stickyElement._hideSticky();
}

if (stickyElement.stickyPosition === StickyConstants.STICKY_POSITION.TOP) {
    stickyElement ._showStickyTop(this._top);
    this ._top += stickyElement.getHeight();
}

if (stickyElement.stickyPosition === StickyConstants.STICKY_POSITION.BOTTOM) {
    stickyElement ._showStickyBottom();
}

}
}

document.addEventListener("DOMContentLoaded", () => {
    window.STICKYMANAGER = new StickyManager();
});
