import {TransitionGroupItemDirective} from "./transition-group-item.directive";
import {
  AfterViewInit, ChangeDetectorRef,
  Component,
  ContentChildren,
  ElementRef, Input,
  OnDestroy,
  QueryList,
  ViewEncapsulation
} from "@angular/core";
import {Subscription} from "rxjs";
import {THREE} from "@angular/cdk/keycodes";

/**
 *  Transition Component for array changes
 *
 *  Call init() method if array is changed. e.g. (different array, insert, delete) and before swap action
 *
 *  If a change is triggered, but you want to avoid transition use disable() - (Your changes) - init() - enable()
 *
 * */
@Component({
  selector: '[transition-group]',
  template: '<ng-content></ng-content>',
  styleUrls: ['transition-group.component.less'],
  encapsulation: ViewEncapsulation.None
})
export class TransitionGroupComponent implements AfterViewInit, OnDestroy {
  @ContentChildren(TransitionGroupItemDirective) items: QueryList<TransitionGroupItemDirective>;
  @Input()
  public id: string;
  public size: number;
  private subscription: Subscription;

  constructor(public element: ElementRef) {}
  
  ngAfterViewInit() {
    this.subscription = this.items.changes.subscribe(items => {
      if(items.length === this.size) {
        items.forEach(item => item.prevPos = item.newPos || item.prevPos);
        items.forEach(this.runCallback);
        this.refreshPosition('newPos');
        items.forEach(item => item.prevPos = item.prevPos || item.newPos); // for new items
  
        const animate = () => {
          items.forEach(this.applyTranslation);
          this['_forceReflow'] = document.body.offsetHeight; // force reflow to put everything in position
          this.items.forEach(this.runTransition.bind(this));
        }
  
        const willMoveSome = items.some((item) => {
          const dx = item.prevPos.left - item.newPos.left;
          const dy = item.prevPos.top - item.newPos.top;
          return dx || dy;
        });
  
        if (willMoveSome) {
          animate();
        } else {
          setTimeout(() => { // for removed items
            this.refreshPosition('newPos');
            animate();
          }, 0);
        }
      }
    });
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  /**
   * Should be used in every array change e.g. (different array, insert, delete)
   * and before swap action
   * */
  init() {
    this.refreshPosition('prevPos');
    this.refreshPosition('newPos');
    this.size = this.items.length;
  }

  runCallback(item: TransitionGroupItemDirective) {
    if (item.moveCallback) {
      item.moveCallback();
    }
  }

  runTransition(item: TransitionGroupItemDirective) {
    if (!item.moved) {
      return;
    }
    const cssClass = 'list-move';
    let el = item.el;
    let style: any = el.style;
    el.classList.add(cssClass);
    style.transform = style.WebkitTransform = style.transitionDuration = '';
    el.addEventListener('transitionend', item.moveCallback = (e: any) => {
      if (!e || /transform$/.test(e.propertyName)) {
        el.removeEventListener('transitionend', item.moveCallback);
        item.moveCallback = null;
        el.classList.remove(cssClass);
      }
    });
  }

  refreshPosition(prop: string) {
    this.items.forEach(item => {
      item[prop] = item.el.getBoundingClientRect();
    });
  }

  applyTranslation(item: TransitionGroupItemDirective) {
    item.moved = false;
    const dx = item.prevPos.left - item.newPos.left;
    const dy = item.prevPos.top - item.newPos.top;
    if (dx || dy) {
      item.moved = true;
      let style: any = item.el.style;
      style.transform = style.WebkitTransform = 'translate(' + dx + 'px,' + dy + 'px)';
      style.transitionDuration = '0s';
    }
  }
  
  /**
   * Enable transition
   * @deprecated
   * */
  enable() {
    console.debug('Deprecated')
  }
  
  /**
   * Disable transition
   * @deprecated
   * */
  disable() {
    console.debug('Deprecated')
  }
}
