import _extends from 'babel-runtime/helpers/extends';
import _Array$from from 'babel-runtime/core-js/array/from';
import React from 'react';
import { findDOMNode } from 'react-dom';
import { Simulate } from 'react-dom/test-utils';
import { render } from '../TestUtils';
import createCellPositionerUtil from './createCellPositioner';
import Masonry from './Masonry';
import { CellMeasurer, CellMeasurerCache } from '../CellMeasurer';

var ALTERNATING_CELL_HEIGHTS = [100, 50, 100, 150];
var CELL_SIZE_MULTIPLIER = 50;
var COLUMN_COUNT = 3;

function assertVisibleCells(rendered, text) {
  expect(_Array$from(rendered.querySelectorAll('.cell')).map(function (node) {
    return node.textContent;
  }).sort().join(',')).toEqual(text);
}

function createCellMeasurerCache() {
  var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

  return new CellMeasurerCache(_extends({
    defaultHeight: CELL_SIZE_MULTIPLIER,
    defaultWidth: CELL_SIZE_MULTIPLIER,
    fixedWidth: true,
    keyMapper: function keyMapper(index) {
      return index;
    }
  }, props));
}

function createCellPositioner(cache) {
  return createCellPositionerUtil({
    cellMeasurerCache: cache,
    columnCount: COLUMN_COUNT,
    columnWidth: CELL_SIZE_MULTIPLIER
  });
}

function createCellRenderer(cache, renderCallback) {
  renderCallback = typeof renderCallback === 'function' ? renderCallback : function (index) {
    return index;
  };

  return function cellRenderer(_ref) {
    var index = _ref.index,
        isScrolling = _ref.isScrolling,
        key = _ref.key,
        parent = _ref.parent,
        style = _ref.style;

    var height = ALTERNATING_CELL_HEIGHTS[index % ALTERNATING_CELL_HEIGHTS.length];
    var width = CELL_SIZE_MULTIPLIER;

    return React.createElement(
      CellMeasurer,
      {
        cache: cache,
        index: index,
        key: key,
        parent: parent
      },
      React.createElement(
        'div',
        {
          className: 'cell',
          ref: function ref(_ref2) {
            if (_ref2) {
              // Accounts for the fact that JSDom doesn't support measurements.
              Object.defineProperty(_ref2, 'offsetHeight', {
                configurable: true,
                value: height
              });
              Object.defineProperty(_ref2, 'offsetWidth', {
                configurable: true,
                value: width
              });
            }
          },
          style: _extends({}, style, {
            minHeight: height,
            minWidth: width
          })
        },
        renderCallback(index, { index: index, isScrolling: isScrolling, key: key, parent: parent, style: style })
      )
    );
  };
}

function getMarkup() {
  var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

  var cellMeasurerCache = props.cellMeasurerCache || createCellMeasurerCache();

  return React.createElement(Masonry, _extends({
    cellCount: 1000,
    cellMeasurerCache: cellMeasurerCache,
    cellPositioner: createCellPositioner(cellMeasurerCache),
    cellRenderer: createCellRenderer(cellMeasurerCache),
    columnCount: COLUMN_COUNT,
    height: CELL_SIZE_MULTIPLIER * 2,
    overscanByPixels: CELL_SIZE_MULTIPLIER,
    width: CELL_SIZE_MULTIPLIER * COLUMN_COUNT
  }, props));
}

function simulateScroll(masonry) {
  var scrollTop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

  var target = { scrollTop: scrollTop };
  masonry._scrollingContainer = target; // HACK to work around _onScroll target check
  Simulate.scroll(findDOMNode(masonry), { target: target });
}

describe('Masonry', function () {
  beforeEach(render.unmount);

  describe('layout and measuring', function () {
    it('should measure only enough cells required for initial render', function () {
      // avg cell size: CELL_SIZE_MULTIPLIER
      // width: CELL_SIZE_MULTIPLIER * 3
      // height: CELL_SIZE_MULTIPLIER * 2
      // overcsan by: CELL_SIZE_MULTIPLIER
      // Expected to measure 9 cells
      var cellMeasurerCache = createCellMeasurerCache();
      render(getMarkup({ cellMeasurerCache: cellMeasurerCache }));
      for (var i = 0; i <= 8; i++) {
        expect(cellMeasurerCache.has(i)).toBe(true);
      }
      expect(cellMeasurerCache.has(9)).toBe(false);
    });

    it('should not measure cells while scrolling until they are needed', function () {
      // Expected to measure 9 cells
      var cellMeasurerCache = createCellMeasurerCache();
      var renderCallback = jest.fn().mockImplementation(function (index) {
        return index;
      });
      var cellRenderer = createCellRenderer(cellMeasurerCache, renderCallback);
      var rendered = findDOMNode(render(getMarkup({ cellMeasurerCache: cellMeasurerCache, cellRenderer: cellRenderer })));
      renderCallback.mockClear();
      // Scroll a little bit, but not so much to require re-measuring
      simulateScroll(rendered, 51);
      // Verify that render was only called enough times to fill view port (no extra for measuring)
      expect(renderCallback).toHaveBeenCalledTimes(7);
    });

    it('should measure additional cells on scroll when it runs out of measured cells', function () {
      var cellMeasurerCache = createCellMeasurerCache();
      var renderCallback = jest.fn().mockImplementation(function (index) {
        return index;
      });
      var cellRenderer = createCellRenderer(cellMeasurerCache, renderCallback);
      var rendered = findDOMNode(render(getMarkup({ cellRenderer: cellRenderer, cellMeasurerCache: cellMeasurerCache })));
      expect(cellMeasurerCache.has(9)).toBe(false);

      renderCallback.mockClear();

      simulateScroll(rendered, 101);
      expect(cellMeasurerCache.has(9)).toBe(true);
      expect(cellMeasurerCache.has(10)).toBe(false);

      // The first batch-measured cell in the new block should be the 10th one
      // Verify that we measured the correct cell...
      expect(renderCallback.mock.calls[0][0]).toBe(9);
    });

    it('should only render enough cells to fill the viewport plus overscanByPixels', function () {
      var rendered = findDOMNode(render(getMarkup()));
      assertVisibleCells(rendered, '0,1,2,3,4,5');
      simulateScroll(rendered, 51);
      assertVisibleCells(rendered, '0,1,2,3,4,5,6');
      simulateScroll(rendered, 101);
      assertVisibleCells(rendered, '0,2,3,4,5,6,7,8');
      simulateScroll(rendered, 1001);
      assertVisibleCells(rendered, '27,29,30,31,32,33,34,35');
    });

    it('should still render correctly when autoHeight is true (eg WindowScroller)', function () {
      // Share instances between renders to avoid resetting state in ways we don't intend
      var cellMeasurerCache = createCellMeasurerCache();
      var cellPositioner = createCellPositioner(cellMeasurerCache);

      var rendered = findDOMNode(render(getMarkup({
        autoHeight: true,
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner
      })));
      assertVisibleCells(rendered, '0,1,2,3,4,5');
      rendered = findDOMNode(render(getMarkup({
        autoHeight: true,
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner,
        scrollTop: 51
      })));
      assertVisibleCells(rendered, '0,1,2,3,4,5,6');
      rendered = findDOMNode(render(getMarkup({
        autoHeight: true,
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner,
        scrollTop: 101
      })));
      assertVisibleCells(rendered, '0,2,3,4,5,6,7,8');
      rendered = findDOMNode(render(getMarkup({
        autoHeight: true,
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner,
        scrollTop: 1001
      })));
      assertVisibleCells(rendered, '27,29,30,31,32,33,34,35');
    });
  });

  describe('recomputeCellPositions', function () {
    it('should refresh all cell positions', function () {
      // Share instances between renders to avoid resetting state in ways we don't intend
      var cellMeasurerCache = createCellMeasurerCache();
      var cellPositioner = jest.fn().mockImplementation(createCellPositioner(cellMeasurerCache));

      var rendered = findDOMNode(render(getMarkup({
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner
      })));
      assertVisibleCells(rendered, '0,1,2,3,4,5');

      cellPositioner.mockImplementation(function (index) {
        return {
          left: 0,
          top: index * CELL_SIZE_MULTIPLIER
        };
      });

      var component = render(getMarkup({
        cellMeasurerCache: cellMeasurerCache,
        cellPositioner: cellPositioner
      }));
      rendered = findDOMNode(component);
      assertVisibleCells(rendered, '0,1,2,3,4,5');
      component.recomputeCellPositions();
      assertVisibleCells(rendered, '0,1,2');
    });

    it('should not reset measurement cache', function () {
      var cellMeasurerCache = createCellMeasurerCache();
      var component = render(getMarkup({ cellMeasurerCache: cellMeasurerCache }));
      var rendered = findDOMNode(component);
      simulateScroll(rendered, 101);
      expect(cellMeasurerCache.has(9)).toBe(true);
      simulateScroll(rendered, 0);
      component.recomputeCellPositions();
      for (var i = 0; i <= 9; i++) {
        expect(cellMeasurerCache.has(i)).toBe(true);
      }
    });
  });

  describe('isScrolling', function () {
    it('should be true for cellRenderer while scrolling is in progress', function () {
      var cellMeasurerCache = createCellMeasurerCache();
      var renderCallback = jest.fn().mockImplementation(function (index) {
        return index;
      });
      var cellRenderer = createCellRenderer(cellMeasurerCache, renderCallback);
      var rendered = findDOMNode(render(getMarkup({ cellMeasurerCache: cellMeasurerCache, cellRenderer: cellRenderer })));
      renderCallback.mockClear();
      simulateScroll(rendered, 51);
      expect(renderCallback.mock.calls[0][1].isScrolling).toEqual(true);
    });

    it('should be reset after a small debounce when scrolling stops', function () {
      jest.useFakeTimers();
      var cellMeasurerCache = createCellMeasurerCache();
      var renderCallback = jest.fn().mockImplementation(function (index) {
        return index;
      });
      var cellRenderer = createCellRenderer(cellMeasurerCache, renderCallback);
      var rendered = findDOMNode(render(getMarkup({ cellMeasurerCache: cellMeasurerCache, cellRenderer: cellRenderer })));
      simulateScroll(rendered, 51);
      renderCallback.mockClear();
      jest.runOnlyPendingTimers();
      expect(renderCallback.mock.calls[0][1].isScrolling).toEqual(false);
    });
  });

  describe('callbacks', function () {
    it('should call onCellsRendered when rendered cells change', function () {
      var onCellsRendered = jest.fn();
      var rendered = findDOMNode(render(getMarkup({ onCellsRendered: onCellsRendered })));
      expect(onCellsRendered.mock.calls).toEqual([[{ startIndex: 0, stopIndex: 5 }]]);
      simulateScroll(rendered, 51);
      expect(onCellsRendered.mock.calls).toEqual([[{ startIndex: 0, stopIndex: 5 }], [{ startIndex: 0, stopIndex: 6 }]]);
      simulateScroll(rendered, 52);
      expect(onCellsRendered.mock.calls).toEqual([[{ startIndex: 0, stopIndex: 5 }], [{ startIndex: 0, stopIndex: 6 }]]);
    });

    it('should call onScroll when scroll position changes', function () {
      var onScroll = jest.fn();
      var rendered = findDOMNode(render(getMarkup({ onScroll: onScroll })));
      expect(onScroll.mock.calls).toEqual([[{ clientHeight: 100, scrollHeight: 16900, scrollTop: 0 }]]);
      simulateScroll(rendered, 51);
      expect(onScroll.mock.calls).toEqual([[{ clientHeight: 100, scrollHeight: 16900, scrollTop: 0 }], [{ clientHeight: 100, scrollHeight: 16900, scrollTop: 51 }]]);
      simulateScroll(rendered, 0);
      expect(onScroll.mock.calls).toEqual([[{ clientHeight: 100, scrollHeight: 16900, scrollTop: 0 }], [{ clientHeight: 100, scrollHeight: 16900, scrollTop: 51 }], [{ clientHeight: 100, scrollHeight: 16900, scrollTop: 0 }]]);
    });
  });

  describe('keyMapper', function () {
    it('should pass the correct key to rendered cells', function () {
      var keyMapper = jest.fn().mockImplementation(function (index) {
        return 'key:' + index;
      });
      var cellRenderer = jest.fn().mockImplementation(function (_ref3) {
        var index = _ref3.index,
            key = _ref3.key,
            style = _ref3.style;
        return React.createElement(
          'div',
          { key: key, style: style },
          index
        );
      });
      findDOMNode(render(getMarkup({ cellRenderer: cellRenderer, keyMapper: keyMapper })));
      expect(keyMapper).toHaveBeenCalled();
      expect(cellRenderer).toHaveBeenCalled();
      expect(cellRenderer.mock.calls[0][0].key).toEqual('key:0');
    });
  });
});