import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
import getNodeName from '@popperjs/core/lib/dom-utils/getNodeName';
import getOffsetParent from '@popperjs/core/lib/dom-utils/getOffsetParent';
import getParentNode from '@popperjs/core/lib/dom-utils/getParentNode';
import getWindow from '@popperjs/core/lib/dom-utils/getWindow';
import getWindowScroll from '@popperjs/core/lib/dom-utils/getWindowScroll';
import { isHTMLElement } from '@popperjs/core/lib/dom-utils/instanceOf';
import isScrollParent from '@popperjs/core/lib/dom-utils/isScrollParent';
import ResizeObserver from 'resize-observer-polyfill';
import { fromEvent, merge, Observable } from 'rxjs';
import * as op from 'rxjs/operators';
import { shallowEqual } from '../utils';

function isWindow(arg) {
  return arg.toString() === '[object Window]' || arg.toString() === '[object global]';
}

function isBody(arg) {
  return getNodeName(arg) === 'body';
}

function isHtml(arg) {
  return getNodeName(arg) === 'html';
}

function isHtmlOrBody(arg) {
  return isHtml(arg) || isBody(arg);
} // 计算从 start（子元素）到 stop（祖先元素）之间所有元素的 scrollTop 或 scrollLeft 的和
// 注意 start 和 stop 都是 INCLUSIVE 的，即两者的 scrollTop 或 scrollLeft 都会统计在内


function accumulateScrollOffset(start, stop, scrollOffsetKey) {
  var result = 0;
  var elem = start;

  while (elem != null) {
    result += elem[scrollOffsetKey];

    if (elem === stop || isWindow(stop) && isHtmlOrBody(elem)) {
      break;
    }

    elem = elem.parentElement;
  }

  if (isWindow(stop)) {
    result += getWindowScroll(elem)[scrollOffsetKey];
  }

  return result;
}
/**
 * 获取 target 相对于 base 的布局大小和相对位置。
 * 注意该方法会考虑滚动所带来的影响
 */


function getRelativeLayoutRect(base, target) {
  if (isWindow(target) || isHtmlOrBody(target)) {
    return {
      left: 0,
      right: window.innerWidth,
      top: 0,
      bottom: window.innerHeight
    };
  }

  var deltaX = 0;
  var deltaY = 0;
  var elem = target;

  while (elem != null && elem != base) {
    deltaY += elem.offsetTop;
    deltaX += elem.offsetLeft;
    var offsetParent = getOffsetParent(elem);
    deltaY -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollTop');
    deltaX -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollLeft');

    if (isWindow(offsetParent)) {
      break;
    }

    deltaY += offsetParent.clientTop;
    deltaX += offsetParent.clientLeft;
    elem = offsetParent;
  }

  return {
    top: deltaY,
    bottom: deltaY + target.offsetHeight,
    left: deltaX,
    right: deltaX + target.offsetWidth
  };
}

function findCommonOffsetAncestor(target, scrollParent) {
  if (isWindow(scrollParent)) {
    return scrollParent;
  }

  var offsetParents = listOffsetParents(target);

  if (_includesInstanceProperty(offsetParents).call(offsetParents, scrollParent)) {
    return scrollParent;
  }

  return getOffsetParent(scrollParent);
} // 列出 target 元素上层的所有 offset parents


function listOffsetParents(target) {
  var result = [];
  var elem = target;

  while (true) {
    if (isWindow(elem)) {
      break;
    }

    elem = getOffsetParent(elem);
    result.push(elem);
  }

  return result;
}

function fromScrollEvent(element) {
  return fromEvent(element, 'scroll', {
    passive: true
  });
}

function fromResizeEvent(element) {
  if (isWindow(element)) {
    return fromEvent(element, 'resize', {
      passive: true
    });
  }

  return new Observable(function (subscriber) {
    var resizeObserver = new ResizeObserver(function (entries) {
      subscriber.next(entries);
    });
    resizeObserver.observe(element);
    return function () {
      resizeObserver.disconnect();
    };
  });
}

function getScrollParent(elem) {
  var _context;

  if (_includesInstanceProperty(_context = ['html', 'body', '#document']).call(_context, getNodeName(elem))) {
    return getWindow(elem);
  }

  if (isHTMLElement(elem) && isScrollParent(elem)) {
    return elem;
  }

  return getScrollParent(getParentNode(elem));
} // 获取 target 相对于「它的滚动父元素」的可见部分的大小与位置


export function getRichVisibleRectsStream(target, structureMayChange$, virtualDebugLabel) {
  return structureMayChange$.pipe(op.startWith('init'), op.map(function () {
    // target 的第一个滚动父元素，我们认为这就是虚拟滚动发生的地方
    // 即虚拟滚动不考虑「更上层元素发生滚动」的情况
    // 直接从父元素开始查找，防止自身设置overflow属性导致是同一个元素
    var scrollParent = getScrollParent(getParentNode(target)); // target 和 scrollParent 的共同 offset 祖先，作为布局尺寸计算时的参照元素

    var commonOffsetAncestor = findCommonOffsetAncestor(target, scrollParent);
    return {
      scrollParent: scrollParent,
      commonOffsetAncestor: commonOffsetAncestor
    };
  }), op.distinctUntilChanged(shallowEqual), op.tap(function (structure) {
    if (virtualDebugLabel) {
      console.log("%c[ali-react-table STRUCTURE ".concat(virtualDebugLabel, "]"), 'color: #4f9052; font-weight: bold', '\ntarget:', target, '\nscrollParent:', structure.scrollParent, '\ncommonOffsetAncestor:', structure.commonOffsetAncestor);
    }
  }), op.switchMap(function (_ref) {
    var scrollParent = _ref.scrollParent,
        commonOffsetAncestor = _ref.commonOffsetAncestor;
    var events$ = merge(fromScrollEvent(scrollParent), fromResizeEvent(scrollParent), fromResizeEvent(target));
    return events$.pipe(op.map(function (event) {
      return {
        targetRect: getRelativeLayoutRect(commonOffsetAncestor, target),
        scrollParentRect: getRelativeLayoutRect(commonOffsetAncestor, scrollParent),
        event: event
      };
    }), op.map(function (_ref2) {
      var event = _ref2.event,
          scrollParentRect = _ref2.scrollParentRect,
          targetRect = _ref2.targetRect;
      return {
        event: event,
        targetRect: targetRect,
        scrollParentRect: scrollParentRect,
        offsetY: Math.max(0, scrollParentRect.top - targetRect.top),
        // 表格的横向滚动总是发生在表格内部，所以这里不需要计算 offsetX
        // offsetX: Math.max(0, scrollParentRect.left - targetRect.left),
        clipRect: {
          left: Math.max(targetRect.left, scrollParentRect.left),
          top: Math.max(targetRect.top, scrollParentRect.top),
          right: Math.min(targetRect.right, scrollParentRect.right),
          bottom: Math.min(targetRect.bottom, scrollParentRect.bottom)
        }
      };
    }));
  }), op.tap(function (rects) {
    if (virtualDebugLabel) {
      console.log("%c[ali-react-table RECTS ".concat(virtualDebugLabel, "]"), 'color: #4f9052; font-weight: bold', '\noffsetY:', rects.offsetY, '\ntargetRect:', rects.targetRect, '\nscrollParentRect:', rects.scrollParentRect, '\nclipRect:', rects.clipRect, '\nevent:', rects.event);
    }
  }));
}