/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 */

import IS_TOUCH_SUPPORTED from "../environment/touchSupport";
import { logger, LogTypes } from "../lib/logger";
import fastSmoothScroll, { ScrollOptions } from "../helpers/fastSmoothScroll";
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import cancelEvent from "../helpers/dom/cancelEvent";
/*
var el = $0;
var height = 0;
var checkUp = false;

do {
  height += el.scrollHeight;
} while(el = (checkUp ? el.previousElementSibling : el.nextElementSibling));
console.log(height);
*/

/*
Array.from($0.querySelectorAll('.bubble-content')).forEach((_el) => {
	//_el.style.display = '';	
	//return;

	let el = _el.parentElement;
	let height = el.scrollHeight;
	let width = el.scrollWidth;
	el.style.width = width + 'px';
	el.style.height = height + 'px';
	_el.style.display = 'none';
});
*/

/* const scrollables: Map<HTMLElement, Scrollable> = new Map();
const scrollsIntersector = new IntersectionObserver((entries) => {
  for(let entry of entries) {
    const scrollable = scrollables.get(entry.target as HTMLElement);

    if(entry.isIntersecting) {
      scrollable.isVisible = true;
    } else {
      scrollable.isVisible = false;

      if(!isInDOM(entry.target)) {
        scrollsIntersector.unobserve(scrollable.container);
        scrollables.delete(scrollable.container);
      }
    }
  }
}); */

export class ScrollableBase {
  protected log: ReturnType<typeof logger>;

  public splitUp: HTMLElement;
  public onScrollMeasure: number = 0;

  public lastScrollPosition: number = 0;
  public lastScrollDirection: number = 0;

  public onAdditionalScroll: () => void;
  public onScrolledTop: () => void;
  public onScrolledBottom: () => void;

  public isHeavyAnimationInProgress = false;
  protected needCheckAfterAnimation = false;

  public checkForTriggers?: () => void;

  public scrollProperty: 'scrollTop' | 'scrollLeft';
  
  protected removeHeavyAnimationListener: () => void;
  protected addedScrollListener: boolean;

  constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
    this.container.classList.add('scrollable');

    this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''), LogTypes.Error);

    if(el) {
      Array.from(el.children).forEach((c) => this.container.append(c));

      el.append(this.container);
    }
    //this.onScroll();
  }

  public addScrollListener() {
    if(this.addedScrollListener) {
      return;
    }

    this.addedScrollListener = true;
    this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
  }
  
  public removeScrollListener() {
    if(!this.addedScrollListener) {
      return;
    }

    this.addedScrollListener = false;
    this.container.removeEventListener('scroll', this.onScroll, {capture: true});
  }

  public setListeners() {
    if(this.removeHeavyAnimationListener) {
      return;
    }

    window.addEventListener('resize', this.onScroll, {passive: true});
    this.addScrollListener();

    this.removeHeavyAnimationListener = useHeavyAnimationCheck(() => {
      this.isHeavyAnimationInProgress = true;

      if(this.onScrollMeasure) {
        this.cancelMeasure();
        this.needCheckAfterAnimation = true;
      }
    }, () => {
      this.isHeavyAnimationInProgress = false;

      if(this.needCheckAfterAnimation) {
        this.onScroll();
        this.needCheckAfterAnimation = false;
      }
    });
  }

  public removeListeners() {
    if(!this.removeHeavyAnimationListener) {
      return;
    }

    window.removeEventListener('resize', this.onScroll);
    this.removeScrollListener();

    this.removeHeavyAnimationListener();
    this.removeHeavyAnimationListener = undefined;
  }

  public append(element: HTMLElement) {
    this.container.append(element);
  }

  public scrollIntoViewNew(options: Omit<ScrollOptions, 'container'>) {
    //return Promise.resolve();
    return fastSmoothScroll({
      ...options,
      container: this.container
    });
  }

  public onScroll = () => {
    //if(this.debug) {
      //this.log('onScroll call', this.onScrollMeasure);
    //}

    //return;

    if(this.isHeavyAnimationInProgress) {
      this.cancelMeasure();
      this.needCheckAfterAnimation = true;
      return;
    }

    //if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
    if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return;
    if(this.onScrollMeasure) return;
    // if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
    this.onScrollMeasure = window.requestAnimationFrame(() => {
      this.onScrollMeasure = 0;

      const scrollPosition = this.container[this.scrollProperty];
      this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top
      this.lastScrollPosition = scrollPosition;

      // lastScrollDirection check is useless here, every callback should decide on its own
      if(this.onAdditionalScroll/*  && this.lastScrollDirection !== 0 */) {
        this.onAdditionalScroll();
      }
      
      if(this.checkForTriggers) {
        this.checkForTriggers();
      }
    });
  };

  public cancelMeasure() {
    if(this.onScrollMeasure) {
      window.cancelAnimationFrame(this.onScrollMeasure);
      this.onScrollMeasure = 0;
    }
  }
}

export type SliceSides = 'top' | 'bottom';
export type SliceSidesContainer = {[k in SliceSides]: boolean};

export default class Scrollable extends ScrollableBase {
  public padding: HTMLElement;
  
  public loadedAll: SliceSidesContainer = {top: true, bottom: false};

  constructor(el: HTMLElement, logPrefix = '', public onScrollOffset = 300, withPaddingContainer?: boolean) {
    super(el, logPrefix);

    /* if(withPaddingContainer) {
      this.padding = document.createElement('div');
      this.padding.classList.add('scrollable-padding');
      Array.from(this.container.children).forEach((c) => this.padding.append(c));
      this.container.append(this.padding);
    } */

    this.container.classList.add('scrollable-y');
    this.setListeners();
    this.scrollProperty = 'scrollTop';
  }

  public setVirtualContainer(el?: HTMLElement) {
    this.splitUp = el;
    this.log('setVirtualContainer:', el, this);
  }

  public checkForTriggers = () => {
    if((!this.onScrolledTop && !this.onScrolledBottom)) return;

    if(this.isHeavyAnimationInProgress) {
      this.onScroll();
      return;
    }

    const scrollHeight = this.container.scrollHeight;
    if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
      return;
    }

    const clientHeight = this.container.clientHeight;
    const maxScrollTop = scrollHeight - clientHeight;
    const scrollTop = this.lastScrollPosition;

    //this.log('checkForTriggers:', scrollTop, maxScrollTop);

    if(this.onScrolledTop && scrollTop <= this.onScrollOffset && this.lastScrollDirection <= 0/* && direction === -1 */) {
      this.onScrolledTop();
    }

    if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && this.lastScrollDirection >= 0/* && direction === 1 */) {
      this.onScrolledBottom();
    }
  };

  public prepend(...elements: (HTMLElement | DocumentFragment)[]) {
    (this.splitUp || this.padding || this.container).prepend(...elements);
  }

  public append(...elements: (HTMLElement | DocumentFragment)[]) {
    (this.splitUp || this.padding || this.container).append(...elements);
  }

  public getDistanceToEnd() {
    return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight);
  }

  get isScrolledDown() {
    return this.getDistanceToEnd() <= 1;
  }

  set scrollTop(y: number) {
    this.container.scrollTop = y;
  }
  
  get scrollTop() {
    //this.log.trace('get scrollTop');
    return this.container.scrollTop;
  }

  public setScrollTopSilently(value: number) {
    this.lastScrollPosition = value;
    this.ignoreNextScrollEvent();

    this.scrollTop = value;
  }

  public ignoreNextScrollEvent() {
    if(this.removeHeavyAnimationListener) {
      this.removeScrollListener();
      this.container.addEventListener('scroll', (e) => {
        cancelEvent(e);
        this.addScrollListener();
      }, {capture: true, passive: false, once: true});
    }
  }
  
  get scrollHeight() {
    return this.container.scrollHeight;
  }
}

export class ScrollableX extends ScrollableBase {
  constructor(el: HTMLElement, logPrefix = '', public onScrollOffset = 300, public splitCount = 15, public container: HTMLElement = document.createElement('div')) {
    super(el, logPrefix, container);

    this.container.classList.add('scrollable-x');

    if(!IS_TOUCH_SUPPORTED) {
      const scrollHorizontally = (e: any) => {
        if(!e.deltaX && this.container.scrollWidth > this.container.clientWidth) {
          this.container.scrollLeft += e.deltaY / 4;
          cancelEvent(e);
        }
      };
      
      this.container.addEventListener('wheel', scrollHorizontally, {passive: false});
    }

    this.scrollProperty = 'scrollLeft';
  }
}
