import { __extends, __assign, __decorate, __metadata } from 'tslib';
import { Prop, Watch, Component } from 'vue-property-decorator';
import BaseComponent from '../BaseComponent.js';
import { findScrollableParent, getParent, getWindow, findIndex } from '@uifabric-vue/utilities';

var MIN_SCROLL_UPDATE_DELAY = 100;
var MAX_SCROLL_UPDATE_DELAY = 500;
var IDLE_DEBOUNCE_DELAY = 200;
var DONE_SCROLLING_WAIT = 500;
var DEFAULT_ITEMS_PER_PAGE = 10;
var DEFAULT_PAGE_HEIGHT = 30;
var DEFAULT_RENDERED_WINDOWS_BEHIND = 2;
var DEFAULT_RENDERED_WINDOWS_AHEAD = 2;
var PAGE_KEY_PREFIX = "page-";
var SPACER_KEY_PREFIX = "spacer-";
var EMPTY_RECT = {
  top: -1,
  bottom: -1,
  left: -1,
  right: -1,
  width: 0,
  height: 0
};
var measurePageRect = function(element) {
  return element.getBoundingClientRect();
};
var _measureSurfaceRect = measurePageRect;
var List = function(_super) {
  __extends(List2, _super);
  function List2() {
    var _this = _super !== null && _super.apply(this, arguments) || this;
    _this.pages = [];
    _this.isScrolling = false;
    _this.measureVersion = 0;
    _this.requiredRect = null;
    _this.materializedRect = null;
    _this.scrollHeight = 0;
    _this.scrollTop = 0;
    _this.requiredWindowsAhead = 0;
    _this.requiredWindowsBehind = 0;
    _this.role = "list";
    _this.focusedIndex = -1;
    _this.totalEstimates = 0;
    _this.hasCompletedFirstRender = false;
    _this.pageCache = {};
    _this.cachedPageHeights = {};
    _this.estimatedPageHeight = 0;
    return _this;
  }
  List2.prototype.render = function(h) {
    var _this = this;
    var _a = this, pages = _a.pages, className = _a.className, css = _a.css, role = _a.role;
    var $pages = pages.map(function(page) {
      return h("div", {
        key: page.key,
        ref: page.key,
        refInFor: true,
        class: "ms-List-page",
        style: _this.getPageStyle(page)
      }, page.items && page.items.map(function(item, index) {
        return h("div", {
          key: index,
          class: "ms-List-cell"
        }, _this.$scopedSlots.item ? _this.$scopedSlots.item({ item: item, index: page.startIndex + index }) : item ? item.name : "");
      }));
    });
    return h("div", {
      ref: "root",
      class: css("ms-List", className),
      attrs: {
        role: pages.length > 0 ? role : void 0
      }
    }, [
      h("div", {
        ref: "surface",
        class: "ms-List-surface",
        attrs: { role: "presentation" }
      }, $pages)
    ]);
  };
  List2.prototype.created = function() {
    this._onScrollingDone = this._async.debounce(this._onScrollingDone, DONE_SCROLLING_WAIT, {
      leading: false
    });
    this._onAsyncScroll = this._async.debounce(this._onAsyncScroll, MIN_SCROLL_UPDATE_DELAY, {
      leading: false,
      maxWait: MAX_SCROLL_UPDATE_DELAY
    });
    this._onAsyncIdle = this._async.debounce(this._onAsyncIdle, IDLE_DEBOUNCE_DELAY, {
      leading: false
    });
  };
  List2.prototype.itemsChanged = function(newVal, oldVal) {
    if (newVal !== oldVal)
      this.updatePages();
  };
  List2.prototype.shouldUpdate = function(newVal, oldVal) {
    if (!newVal && oldVal)
      this.updatePages();
  };
  List2.prototype.mounted = function() {
    this.updatePages();
    this.measureVersion++;
    this.scrollElement = findScrollableParent(this.$refs.root);
    if (this.$refs.root) {
      this.events.on(this.$refs.root, "focus", this._onFocus, true);
    }
    if (this.scrollElement) {
      this.events.on(this.scrollElement, "scroll", this._onScroll);
      this.events.on(this.scrollElement, "scroll", this._onAsyncScroll);
    }
  };
  List2.prototype._onAsyncScroll = function() {
    this.updateRenderRects();
    if (!this.materializedRect || !_isContainedWithin(this.requiredRect, this.materializedRect)) {
      this.updatePages();
    }
  };
  List2.prototype._onAsyncIdle = function() {
    var _a = this, renderedWindowsAhead = _a.renderedWindowsAhead, renderedWindowsBehind = _a.renderedWindowsBehind;
    var _b = this, requiredWindowsAhead = _b.requiredWindowsAhead, requiredWindowsBehind = _b.requiredWindowsBehind;
    var windowsAhead = Math.min(renderedWindowsAhead, requiredWindowsAhead + 1);
    var windowsBehind = Math.min(renderedWindowsBehind, requiredWindowsBehind + 1);
    if (windowsAhead !== requiredWindowsAhead || windowsBehind !== requiredWindowsBehind) {
      this.requiredWindowsAhead = windowsAhead;
      this.requiredWindowsBehind = windowsBehind;
      this.updateRenderRects();
      this.updatePages();
    }
    if (renderedWindowsAhead > windowsAhead || renderedWindowsBehind > windowsBehind) {
      this._onAsyncIdle();
    }
  };
  List2.prototype._onFocus = function(ev) {
    var target = ev.target;
    while (target !== this.$refs.surface) {
      var indexString = target.getAttribute("data-list-index");
      if (indexString) {
        this.focusedIndex = Number(indexString);
        break;
      }
      target = getParent(target);
    }
  };
  List2.prototype._onScroll = function() {
    if (!this.isScrolling && !this.ignoreScrollingState) {
      this.isScrolling = true;
    }
    this.resetRequiredWindows();
    this._onScrollingDone();
  };
  List2.prototype.resetRequiredWindows = function() {
    this.requiredWindowsAhead = 0;
    this.requiredWindowsBehind = 0;
  };
  List2.prototype._onScrollingDone = function() {
    if (!this.ignoreScrollingState) {
      this.isScrolling = false;
    }
  };
  List2.prototype.getPageStyle = function(page) {
    var getPageStyle = this.$props.getPageStyle;
    return __assign(__assign({}, getPageStyle ? getPageStyle(page) : {}), !page.items ? {
      height: page.height + "px"
    } : {});
  };
  List2.prototype._buildPages = function(props) {
    var renderCount = props.renderCount;
    var items = props.items, startIndex = props.startIndex, getPageHeight = props.getPageHeight;
    renderCount = this._getRenderCount(props);
    var materializedRect = __assign({}, EMPTY_RECT);
    var pages = [];
    var itemsPerPage = 1;
    var pageTop = 0;
    var currentSpacer = null;
    var focusedIndex = this.focusedIndex;
    var endIndex = startIndex + renderCount;
    var shouldVirtualize = this.shouldVirtualize(props);
    var isFirstRender = this.estimatedPageHeight === 0 && !getPageHeight;
    var allowedRect = this.allowedRect;
    var _loop_1 = function(itemIndex2) {
      var pageSpecification = this_1._getPageSpecification(itemIndex2, allowedRect);
      var pageHeight = pageSpecification.height;
      var pageData = pageSpecification.data;
      var key = pageSpecification.key;
      itemsPerPage = pageSpecification.itemCount;
      var pageBottom = pageTop + pageHeight - 1;
      var isPageRendered = findIndex(this_1.pages, function(page) {
        return !!page.items && page.startIndex === itemIndex2;
      }) > -1;
      var isPageInAllowedRange = !allowedRect || pageBottom >= allowedRect.top && pageTop <= allowedRect.bottom;
      var isPageInRequiredRange = !this_1.requiredRect || pageBottom >= this_1.requiredRect.top && pageTop <= this_1.requiredRect.bottom;
      var isPageVisible = !isFirstRender && (isPageInRequiredRange || isPageInAllowedRange && isPageRendered) || !shouldVirtualize;
      var isPageFocused = focusedIndex >= itemIndex2 && focusedIndex < itemIndex2 + itemsPerPage;
      var isFirstPage = itemIndex2 === startIndex;
      if (isPageVisible || isPageFocused || isFirstPage) {
        if (currentSpacer) {
          pages.push(currentSpacer);
          currentSpacer = null;
        }
        var itemsInPage = Math.min(itemsPerPage, endIndex - itemIndex2);
        var newPage = this_1._createPage(key, items.slice(itemIndex2, itemIndex2 + itemsInPage), itemIndex2, void 0, void 0, pageData);
        newPage.top = pageTop;
        newPage.height = pageHeight;
        if (this_1.visibleRect && this_1.visibleRect.bottom) {
          newPage.isVisible = pageBottom >= this_1.visibleRect.top && pageTop <= this_1.visibleRect.bottom;
        }
        pages.push(newPage);
        if (isPageInRequiredRange && this_1.allowedRect) {
          _mergeRect(materializedRect, {
            top: pageTop,
            bottom: pageBottom,
            height: pageHeight,
            left: allowedRect.left,
            right: allowedRect.right,
            width: allowedRect.width
          });
        }
      } else {
        if (!currentSpacer) {
          currentSpacer = this_1._createPage(SPACER_KEY_PREFIX + itemIndex2, void 0, itemIndex2, 0, void 0, pageData, true);
        }
        currentSpacer.height = (currentSpacer.height || 0) + (pageBottom - pageTop) + 1;
        currentSpacer.itemCount += itemsPerPage;
      }
      pageTop += pageBottom - pageTop + 1;
      if (isFirstRender && shouldVirtualize) {
        return "break";
      }
    };
    var this_1 = this;
    for (var itemIndex = startIndex; itemIndex < endIndex; itemIndex += itemsPerPage) {
      var state_1 = _loop_1(itemIndex);
      if (state_1 === "break")
        break;
    }
    if (currentSpacer) {
      currentSpacer.key = SPACER_KEY_PREFIX + "end";
      pages.push(currentSpacer);
    }
    this.materializedRect = materializedRect;
    return {
      pages: pages,
      measureVersion: this.measureVersion
    };
  };
  List2.prototype._getPageSpecification = function(itemIndex, visibleRect) {
    var getPageSpecification = this.$props.getPageSpecification;
    if (getPageSpecification) {
      var pageData = getPageSpecification(itemIndex, visibleRect);
      var _a = pageData.itemCount, itemCount = _a === void 0 ? this._getItemCountForPage(itemIndex, visibleRect) : _a;
      var _b = pageData.height, height = _b === void 0 ? this._getPageHeight(itemIndex, visibleRect, itemCount) : _b;
      return {
        itemCount: itemCount,
        height: height,
        data: pageData.data,
        key: pageData.key
      };
    } else {
      var itemCount = this._getItemCountForPage(itemIndex, visibleRect);
      return {
        itemCount: itemCount,
        height: this._getPageHeight(itemIndex, visibleRect, itemCount)
      };
    }
  };
  List2.prototype._getPageHeight = function(itemIndex, visibleRect, itemsPerPage) {
    if (this.getPageHeight) {
      return this.getPageHeight(itemIndex, visibleRect, itemsPerPage);
    } else {
      var cachedHeight = this.cachedPageHeights[itemIndex];
      return cachedHeight ? cachedHeight.height : this.estimatedPageHeight || DEFAULT_PAGE_HEIGHT;
    }
  };
  List2.prototype._getItemCountForPage = function(itemIndex, visibileRect) {
    var itemsPerPage = this.getItemCountForPage ? this.getItemCountForPage(itemIndex, visibileRect) : DEFAULT_ITEMS_PER_PAGE;
    return itemsPerPage || DEFAULT_ITEMS_PER_PAGE;
  };
  List2.prototype._onPageAdded = function(page) {
    var onPageAdded = this.$props.onPageAdded;
    if (onPageAdded) {
      onPageAdded(page);
    }
  };
  List2.prototype._onPageRemoved = function(page) {
    var onPageRemoved = this.$props.onPageRemoved;
    if (onPageRemoved) {
      onPageRemoved(page);
    }
  };
  List2.prototype.notifyPageChanges = function(oldPages, newPages, props) {
    if (props === void 0) {
      props = this.$props;
    }
    var onPageAdded = props.onPageAdded, onPageRemoved = props.onPageRemoved;
    if (onPageAdded || onPageRemoved) {
      var renderedIndexes = {};
      for (var _i = 0, oldPages_1 = oldPages; _i < oldPages_1.length; _i++) {
        var page = oldPages_1[_i];
        if (page.items) {
          renderedIndexes[page.startIndex] = page;
        }
      }
      for (var _a = 0, newPages_1 = newPages; _a < newPages_1.length; _a++) {
        var page = newPages_1[_a];
        if (page.items) {
          if (!renderedIndexes[page.startIndex]) {
            this._onPageAdded(page);
          } else {
            delete renderedIndexes[page.startIndex];
          }
        }
      }
      for (var index in renderedIndexes) {
        if (renderedIndexes.hasOwnProperty(index)) {
          this._onPageRemoved(renderedIndexes[index]);
        }
      }
    }
  };
  List2.prototype.updatePages = function(props) {
    if (props === void 0) {
      props = this.$props;
    }
    if (!this.requiredRect) {
      this.updateRenderRects(props);
    }
    var newListState = this._buildPages(props);
    var oldListPages = this.pages;
    this.notifyPageChanges(oldListPages, newListState.pages);
    this.measureVersion = newListState.measureVersion;
    this.pages = newListState.pages;
    var finalProps = this.$props;
    var finalState = this;
    if (!finalProps.getPageHeight) {
      var heightsChanged = this.updatePageMeasurements(finalState.pages);
      if (heightsChanged) {
        this.materializedRect = null;
        if (!this.hasCompletedFirstRender) {
          this.hasCompletedFirstRender = true;
          this.updatePages(finalProps);
        } else {
          this._onAsyncScroll();
        }
      } else {
        this._onAsyncIdle();
      }
    } else {
      this._onAsyncIdle();
    }
    if (finalProps.onPagesUpdated) {
      finalProps.onPagesUpdated(finalState.pages);
    }
  };
  List2.prototype.updatePageMeasurements = function(pages) {
    var heightChanged = false;
    if (!this.shouldVirtualize()) {
      return heightChanged;
    }
    for (var i = 0; i < pages.length; i++) {
      var page = pages[i];
      if (page.items) {
        heightChanged = this.measurePage(page) || heightChanged;
      }
    }
    return heightChanged;
  };
  List2.prototype.measurePage = function(page) {
    var hasChangedHeight = false;
    var pageElement = this.$refs[page.key] ? this.$refs[page.key][0] : null;
    var cachedHeight = this.cachedPageHeights[page.startIndex];
    if (pageElement && this.shouldVirtualize() && (!cachedHeight || cachedHeight.measureVersion !== this.measureVersion)) {
      var newClientRect = {
        width: pageElement.clientWidth,
        height: pageElement.clientHeight
      };
      if (newClientRect.height || newClientRect.width) {
        hasChangedHeight = page.height !== newClientRect.height;
        page.height = newClientRect.height;
        this.cachedPageHeights[page.startIndex] = {
          height: newClientRect.height,
          measureVersion: this.measureVersion
        };
        this.estimatedPageHeight = Math.round((this.estimatedPageHeight * this.totalEstimates + newClientRect.height) / (this.totalEstimates + 1));
        this.totalEstimates++;
      }
    }
    return hasChangedHeight;
  };
  List2.prototype._createPage = function(pageKey, items, startIndex, count, style, data, isSpacer) {
    if (startIndex === void 0) {
      startIndex = -1;
    }
    if (count === void 0) {
      count = items ? items.length : 0;
    }
    if (style === void 0) {
      style = {};
    }
    pageKey = pageKey || PAGE_KEY_PREFIX + startIndex;
    var cachedPage = this.pageCache[pageKey];
    if (cachedPage && cachedPage.page) {
      return cachedPage.page;
    }
    return {
      key: pageKey,
      startIndex: startIndex,
      itemCount: count,
      items: items,
      style: style,
      top: 0,
      height: 0,
      data: data,
      isSpacer: isSpacer || false
    };
  };
  List2.prototype._getRenderCount = function(props) {
    var _a = props || this.$props, items = _a.items, startIndex = _a.startIndex, renderCount = _a.renderCount;
    return renderCount === void 0 ? items ? items.length - startIndex : 0 : renderCount;
  };
  List2.prototype.shouldVirtualize = function(props) {
    if (props === void 0) {
      props = this.$props;
    }
    var onShouldVirtualize = this.$props.onShouldVirtualize;
    return !onShouldVirtualize || onShouldVirtualize(props);
  };
  List2.prototype.updateRenderRects = function(props, forceUpdate) {
    props = props || this.$props;
    var renderedWindowsAhead = props.renderedWindowsAhead, renderedWindowsBehind = props.renderedWindowsBehind;
    var pages = this.pages;
    if (!this.shouldVirtualize(props)) {
      return;
    }
    var surfaceRect = this.surfaceRect || __assign({}, EMPTY_RECT);
    var scrollHeight = this.scrollElement && this.scrollElement.scrollHeight;
    var scrollTop = this.scrollElement ? this.scrollElement.scrollTop : 0;
    if (this.$refs.surface && (forceUpdate || !pages || !this.surfaceRect || !scrollHeight || scrollHeight !== this.scrollHeight || Math.abs(this.scrollTop - scrollTop) > this.estimatedPageHeight / 3)) {
      surfaceRect = this.surfaceRect = _measureSurfaceRect(this.$refs.surface);
      this.scrollTop = scrollTop;
    }
    if (forceUpdate || !scrollHeight || scrollHeight !== this.scrollHeight) {
      this.measureVersion++;
    }
    this.scrollHeight = scrollHeight;
    var visibleTop = Math.max(0, -surfaceRect.top);
    var win = getWindow(this.$refs.root);
    var visibleRect = {
      top: visibleTop,
      left: surfaceRect.left,
      bottom: visibleTop + win.innerHeight,
      right: surfaceRect.right,
      width: surfaceRect.width,
      height: win.innerHeight
    };
    this.requiredRect = _expandRect(visibleRect, this.requiredWindowsBehind, this.requiredWindowsAhead);
    this.allowedRect = _expandRect(visibleRect, renderedWindowsBehind, renderedWindowsAhead);
    this.visibleRect = visibleRect;
  };
  __decorate([
    Prop({ type: Array, required: true }),
    __metadata("design:type", Object)
  ], List2.prototype, "items", void 0);
  __decorate([
    Prop({ type: Function, default: null }),
    __metadata("design:type", Function)
  ], List2.prototype, "getItemCountForPage", void 0);
  __decorate([
    Prop({ type: Function, default: null }),
    __metadata("design:type", Function)
  ], List2.prototype, "getPageHeight", void 0);
  __decorate([
    Prop({ type: Number, default: DEFAULT_RENDERED_WINDOWS_AHEAD }),
    __metadata("design:type", Number)
  ], List2.prototype, "renderedWindowsAhead", void 0);
  __decorate([
    Prop({ type: Number, default: DEFAULT_RENDERED_WINDOWS_BEHIND }),
    __metadata("design:type", Number)
  ], List2.prototype, "renderedWindowsBehind", void 0);
  __decorate([
    Prop({ type: Number, default: 0 }),
    __metadata("design:type", Number)
  ], List2.prototype, "startIndex", void 0);
  __decorate([
    Prop({ type: Boolean, default: false }),
    __metadata("design:type", Boolean)
  ], List2.prototype, "ignoreScrollingState", void 0);
  __decorate([
    Watch("items"),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", void 0)
  ], List2.prototype, "itemsChanged", null);
  __decorate([
    Watch("isScrolling"),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Boolean, Boolean]),
    __metadata("design:returntype", void 0)
  ], List2.prototype, "shouldUpdate", null);
  List2 = __decorate([
    Component({
      components: {}
    })
  ], List2);
  return List2;
}(BaseComponent);
function _expandRect(rect, pagesBefore, pagesAfter) {
  var top = rect.top - pagesBefore * rect.height;
  var height = rect.height + (pagesBefore + pagesAfter) * rect.height;
  return {
    top: top,
    bottom: top + height,
    height: height,
    left: rect.left,
    right: rect.right,
    width: rect.width
  };
}
function _isContainedWithin(innerRect, outerRect) {
  return innerRect.top >= outerRect.top && innerRect.left >= outerRect.left && innerRect.bottom <= outerRect.bottom && innerRect.right <= outerRect.right;
}
function _mergeRect(targetRect, newRect) {
  targetRect.top = newRect.top < targetRect.top || targetRect.top === -1 ? newRect.top : targetRect.top;
  targetRect.left = newRect.left < targetRect.left || targetRect.left === -1 ? newRect.left : targetRect.left;
  targetRect.bottom = newRect.bottom > targetRect.bottom || targetRect.bottom === -1 ? newRect.bottom : targetRect.bottom;
  targetRect.right = newRect.right > targetRect.right || targetRect.right === -1 ? newRect.right : targetRect.right;
  targetRect.width = targetRect.right - targetRect.left + 1;
  targetRect.height = targetRect.bottom - targetRect.top + 1;
  return targetRect;
}

export { List };
