import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridRowCountSelector, gridRowsLookupSelector, gridRowTreeSelector, gridRowGroupingNameSelector, gridRowTreeDepthsSelector, gridDataRowIdsSelector, gridRowsDataRowIdToIdLookupSelector, gridRowMaximumTreeDepthSelector } from './gridRowsSelector';
import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { useGridVisibleRows } from '../../utils/useGridVisibleRows';
import { gridSortedRowIdsSelector } from '../sorting/gridSortingSelector';
import { gridFilteredRowsLookupSelector } from '../filter/gridFilterSelector';
import { getTreeNodeDescendants, createRowsInternalCache, getRowsStateFromCache, isAutoGeneratedRow, GRID_ROOT_GROUP_ID, updateCacheWithNewRows, getTopLevelRowCount, getRowIdFromRowModel } from './gridRowsUtils';
import { useGridRegisterPipeApplier } from '../../core/pipeProcessing';
export var rowsStateInitializer = function rowsStateInitializer(state, props, apiRef) {
  apiRef.current.caches.rows = createRowsInternalCache({
    rows: props.rows,
    getRowId: props.getRowId,
    loading: props.loading,
    rowCount: props.rowCount
  });
  return _extends({}, state, {
    rows: getRowsStateFromCache({
      apiRef: apiRef,
      rowCountProp: props.rowCount,
      loadingProp: props.loading,
      previousTree: null,
      previousTreeDepths: null
    })
  });
};
export var useGridRows = function useGridRows(apiRef, props) {
  if (process.env.NODE_ENV !== 'production') {
    try {
      // Freeze the `rows` prop so developers have a fast failure if they try to use Array.prototype.push().
      Object.freeze(props.rows);
    } catch (error) {
      // Sometimes, it's impossible to freeze, so we give up on it.
    }
  }
  var logger = useGridLogger(apiRef, 'useGridRows');
  var currentPage = useGridVisibleRows(apiRef, props);
  var lastUpdateMs = React.useRef(Date.now());
  var timeout = React.useRef(null);
  var getRow = React.useCallback(function (id) {
    var model = gridRowsLookupSelector(apiRef)[id];
    if (model) {
      return model;
    }
    var node = apiRef.current.getRowNode(id);
    if (node && isAutoGeneratedRow(node)) {
      // TODO rows v6: Is it the best approach ?
      return {};
    }
    return null;
  }, [apiRef]);
  var lookup = React.useMemo(function () {
    return currentPage.rows.reduce(function (acc, _ref, index) {
      var id = _ref.id;
      acc[id] = index;
      return acc;
    }, {});
  }, [currentPage.rows]);
  var throttledRowsChange = React.useCallback(function (_ref2) {
    var cache = _ref2.cache,
      throttle = _ref2.throttle;
    var run = function run() {
      timeout.current = null;
      lastUpdateMs.current = Date.now();
      apiRef.current.setState(function (state) {
        return _extends({}, state, {
          rows: getRowsStateFromCache({
            apiRef: apiRef,
            rowCountProp: props.rowCount,
            loadingProp: props.loading,
            previousTree: gridRowTreeSelector(apiRef),
            previousTreeDepths: gridRowTreeDepthsSelector(apiRef)
          })
        });
      });
      apiRef.current.publishEvent('rowsSet');
      apiRef.current.forceUpdate();
    };
    if (timeout.current) {
      clearTimeout(timeout.current);
      timeout.current = null;
    }
    apiRef.current.caches.rows = cache;
    if (!throttle) {
      run();
      return;
    }
    var throttleRemainingTimeMs = props.throttleRowsMs - (Date.now() - lastUpdateMs.current);
    if (throttleRemainingTimeMs > 0) {
      timeout.current = setTimeout(run, throttleRemainingTimeMs);
      return;
    }
    run();
  }, [props.throttleRowsMs, props.rowCount, props.loading, apiRef]);

  /**
   * API METHODS
   */
  var setRows = React.useCallback(function (rows) {
    logger.debug("Updating all rows, new length ".concat(rows.length));
    var cache = createRowsInternalCache({
      rows: rows,
      getRowId: props.getRowId,
      loading: props.loading,
      rowCount: props.rowCount
    });
    var prevCache = apiRef.current.caches.rows;
    cache.rowsBeforePartialUpdates = prevCache.rowsBeforePartialUpdates;
    throttledRowsChange({
      cache: cache,
      throttle: true
    });
  }, [logger, props.getRowId, props.loading, props.rowCount, throttledRowsChange, apiRef]);
  var updateRows = React.useCallback(function (updates) {
    if (props.signature === GridSignature.DataGrid && updates.length > 1) {
      throw new Error(["MUI: You can't update several rows at once in `apiRef.current.updateRows` on the DataGrid.", 'You need to upgrade to DataGridPro or DataGridPremium component to unlock this feature.'].join('\n'));
    }
    var nonPinnedRowsUpdates = [];
    updates.forEach(function (update) {
      var id = getRowIdFromRowModel(update, props.getRowId, 'A row was provided without id when calling updateRows():');
      var rowNode = apiRef.current.getRowNode(id);
      if ((rowNode == null ? void 0 : rowNode.type) === 'pinnedRow') {
        // @ts-ignore because otherwise `release:build` doesn't work
        var pinnedRowsCache = apiRef.current.caches.pinnedRows;
        var prevModel = pinnedRowsCache.idLookup[id];
        if (prevModel) {
          pinnedRowsCache.idLookup[id] = _extends({}, prevModel, update);
        }
      } else {
        nonPinnedRowsUpdates.push(update);
      }
    });
    var cache = updateCacheWithNewRows({
      updates: nonPinnedRowsUpdates,
      getRowId: props.getRowId,
      previousCache: apiRef.current.caches.rows
    });
    throttledRowsChange({
      cache: cache,
      throttle: true
    });
  }, [props.signature, props.getRowId, throttledRowsChange, apiRef]);
  var getRowModels = React.useCallback(function () {
    var dataRows = gridDataRowIdsSelector(apiRef);
    var idRowsLookup = gridRowsLookupSelector(apiRef);
    return new Map(dataRows.map(function (id) {
      var _idRowsLookup$id;
      return [id, (_idRowsLookup$id = idRowsLookup[id]) != null ? _idRowsLookup$id : {}];
    }));
  }, [apiRef]);
  var getRowsCount = React.useCallback(function () {
    return gridRowCountSelector(apiRef);
  }, [apiRef]);
  var getAllRowIds = React.useCallback(function () {
    return gridDataRowIdsSelector(apiRef);
  }, [apiRef]);
  var getRowIndexRelativeToVisibleRows = React.useCallback(function (id) {
    return lookup[id];
  }, [lookup]);
  var setRowChildrenExpansion = React.useCallback(function (id, isExpanded) {
    var currentNode = apiRef.current.getRowNode(id);
    if (!currentNode) {
      throw new Error("MUI: No row with id #".concat(id, " found"));
    }
    if (currentNode.type !== 'group') {
      throw new Error('MUI: Only group nodes can be expanded or collapsed');
    }
    var newNode = _extends({}, currentNode, {
      childrenExpanded: isExpanded
    });
    apiRef.current.setState(function (state) {
      return _extends({}, state, {
        rows: _extends({}, state.rows, {
          tree: _extends({}, state.rows.tree, _defineProperty({}, id, newNode))
        })
      });
    });
    apiRef.current.forceUpdate();
    apiRef.current.publishEvent('rowExpansionChange', newNode);
  }, [apiRef]);
  var getRowNode = React.useCallback(function (id) {
    var _ref3;
    return (_ref3 = gridRowTreeSelector(apiRef)[id]) != null ? _ref3 : null;
  }, [apiRef]);
  var getRowGroupChildren = React.useCallback(function (_ref4) {
    var _ref4$skipAutoGenerat = _ref4.skipAutoGeneratedRows,
      skipAutoGeneratedRows = _ref4$skipAutoGenerat === void 0 ? true : _ref4$skipAutoGenerat,
      groupId = _ref4.groupId,
      applySorting = _ref4.applySorting,
      applyFiltering = _ref4.applyFiltering;
    var tree = gridRowTreeSelector(apiRef);
    var children;
    if (applySorting) {
      var groupNode = tree[groupId];
      if (!groupNode) {
        return [];
      }
      var sortedRowIds = gridSortedRowIdsSelector(apiRef);
      children = [];
      var startIndex = sortedRowIds.findIndex(function (id) {
        return id === groupId;
      }) + 1;
      for (var index = startIndex; index < sortedRowIds.length && tree[sortedRowIds[index]].depth > groupNode.depth; index += 1) {
        var id = sortedRowIds[index];
        if (!skipAutoGeneratedRows || !isAutoGeneratedRow(tree[id])) {
          children.push(id);
        }
      }
    } else {
      children = getTreeNodeDescendants(tree, groupId, skipAutoGeneratedRows);
    }
    if (applyFiltering) {
      var filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
      children = children.filter(function (childId) {
        return filteredRowsLookup[childId] !== false;
      });
    }
    return children;
  }, [apiRef]);
  var setRowIndex = React.useCallback(function (rowId, targetIndex) {
    var node = apiRef.current.getRowNode(rowId);
    if (!node) {
      throw new Error("MUI: No row with id #".concat(rowId, " found"));
    }
    if (node.parent !== GRID_ROOT_GROUP_ID) {
      throw new Error("MUI: The row reordering do not support reordering of grouped rows yet");
    }
    if (node.type !== 'leaf') {
      throw new Error("MUI: The row reordering do not support reordering of footer or grouping rows");
    }
    apiRef.current.setState(function (state) {
      var group = gridRowTreeSelector(state, apiRef.current.instanceId)[GRID_ROOT_GROUP_ID];
      var allRows = group.children;
      var oldIndex = allRows.findIndex(function (row) {
        return row === rowId;
      });
      if (oldIndex === -1 || oldIndex === targetIndex) {
        return state;
      }
      logger.debug("Moving row ".concat(rowId, " to index ").concat(targetIndex));
      var updatedRows = _toConsumableArray(allRows);
      updatedRows.splice(targetIndex, 0, updatedRows.splice(oldIndex, 1)[0]);
      return _extends({}, state, {
        rows: _extends({}, state.rows, {
          tree: _extends({}, state.rows.tree, _defineProperty({}, GRID_ROOT_GROUP_ID, _extends({}, group, {
            children: updatedRows
          })))
        })
      });
    });
    apiRef.current.publishEvent('rowsSet');
  }, [apiRef, logger]);
  var replaceRows = React.useCallback(function (firstRowToRender, newRows) {
    if (props.signature === GridSignature.DataGrid && newRows.length > 1) {
      throw new Error(["MUI: You can't replace rows using `apiRef.current.unstable_replaceRows` on the DataGrid.", 'You need to upgrade to DataGridPro or DataGridPremium component to unlock this feature.'].join('\n'));
    }
    if (newRows.length === 0) {
      return;
    }
    var treeDepth = gridRowMaximumTreeDepthSelector(apiRef);
    if (treeDepth > 1) {
      throw new Error('`apiRef.current.unstable_replaceRows` is not compatible with tree data and row grouping');
    }
    var tree = _extends({}, gridRowTreeSelector(apiRef));
    var dataRowIdToModelLookup = _extends({}, gridRowsLookupSelector(apiRef));
    var dataRowIdToIdLookup = _extends({}, gridRowsDataRowIdToIdLookupSelector(apiRef));
    var rootGroup = tree[GRID_ROOT_GROUP_ID];
    var rootGroupChildren = _toConsumableArray(rootGroup.children);
    for (var i = 0; i < newRows.length; i += 1) {
      var rowModel = newRows[i];
      var rowId = getRowIdFromRowModel(rowModel, props.getRowId, 'A row was provided without id when calling replaceRows().');
      var _rootGroupChildren$sp = rootGroupChildren.splice(firstRowToRender + i, 1, rowId),
        _rootGroupChildren$sp2 = _slicedToArray(_rootGroupChildren$sp, 1),
        replacedRowId = _rootGroupChildren$sp2[0];
      delete dataRowIdToModelLookup[replacedRowId];
      delete dataRowIdToIdLookup[replacedRowId];
      delete tree[replacedRowId];
      var rowTreeNodeConfig = {
        id: rowId,
        depth: 0,
        parent: GRID_ROOT_GROUP_ID,
        type: 'leaf',
        groupingKey: null
      };
      dataRowIdToModelLookup[rowId] = rowModel;
      dataRowIdToIdLookup[rowId] = rowId;
      tree[rowId] = rowTreeNodeConfig;
    }
    tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, {
      children: rootGroupChildren
    });

    // Removes potential remaining skeleton rows from the dataRowIds.
    var dataRowIds = rootGroupChildren.filter(function (childId) {
      return tree[childId].type === 'leaf';
    });
    apiRef.current.caches.rows.dataRowIdToModelLookup = dataRowIdToModelLookup;
    apiRef.current.caches.rows.dataRowIdToIdLookup = dataRowIdToIdLookup;
    apiRef.current.setState(function (state) {
      return _extends({}, state, {
        rows: _extends({}, state.rows, {
          dataRowIdToModelLookup: dataRowIdToModelLookup,
          dataRowIdToIdLookup: dataRowIdToIdLookup,
          dataRowIds: dataRowIds,
          tree: tree
        })
      });
    });
    apiRef.current.publishEvent('rowsSet');
  }, [apiRef, props.signature, props.getRowId]);
  var rowApi = {
    getRow: getRow,
    getRowModels: getRowModels,
    getRowsCount: getRowsCount,
    getAllRowIds: getAllRowIds,
    setRows: setRows,
    updateRows: updateRows,
    getRowNode: getRowNode,
    getRowIndexRelativeToVisibleRows: getRowIndexRelativeToVisibleRows,
    unstable_replaceRows: replaceRows
  };
  var rowProApi = {
    setRowIndex: setRowIndex,
    setRowChildrenExpansion: setRowChildrenExpansion,
    getRowGroupChildren: getRowGroupChildren
  };

  /**
   * EVENTS
   */
  var groupRows = React.useCallback(function () {
    logger.info("Row grouping pre-processing have changed, regenerating the row tree");
    var cache;
    if (apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows) {
      // The `props.rows` did not change since the last row grouping
      // We can use the current rows cache which contains the partial updates done recently.
      cache = _extends({}, apiRef.current.caches.rows, {
        updates: {
          type: 'full',
          rows: gridDataRowIdsSelector(apiRef)
        }
      });
    } else {
      // The `props.rows` has changed since the last row grouping
      // We must use the new `props.rows` on the new grouping
      // This occurs because this event is triggered before the `useEffect` on the rows when both the grouping pre-processing and the rows changes on the same render
      cache = createRowsInternalCache({
        rows: props.rows,
        getRowId: props.getRowId,
        loading: props.loading,
        rowCount: props.rowCount
      });
    }
    throttledRowsChange({
      cache: cache,
      throttle: false
    });
  }, [logger, apiRef, props.rows, props.getRowId, props.loading, props.rowCount, throttledRowsChange]);
  var handleStrategyProcessorChange = React.useCallback(function (methodName) {
    if (methodName === 'rowTreeCreation') {
      groupRows();
    }
  }, [groupRows]);
  var handleStrategyActivityChange = React.useCallback(function () {
    // `rowTreeCreation` is the only processor ran when `strategyAvailabilityChange` is fired.
    // All the other processors listen to `rowsSet` which will be published by the `groupRows` method below.
    if (apiRef.current.getActiveStrategy('rowTree') !== gridRowGroupingNameSelector(apiRef)) {
      groupRows();
    }
  }, [apiRef, groupRows]);
  useGridApiEventHandler(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange);
  useGridApiEventHandler(apiRef, 'strategyAvailabilityChange', handleStrategyActivityChange);

  /**
   * APPLIERS
   */
  var applyHydrateRowsProcessor = React.useCallback(function () {
    apiRef.current.setState(function (state) {
      var response = apiRef.current.unstable_applyPipeProcessors('hydrateRows', {
        tree: gridRowTreeSelector(state, apiRef.current.instanceId),
        treeDepths: gridRowTreeDepthsSelector(state, apiRef.current.instanceId),
        dataRowIds: gridDataRowIdsSelector(state, apiRef.current.instanceId),
        dataRowIdToModelLookup: gridRowsLookupSelector(state, apiRef.current.instanceId),
        dataRowIdToIdLookup: gridRowsDataRowIdToIdLookupSelector(state, apiRef.current.instanceId)
      });
      return _extends({}, state, {
        rows: _extends({}, state.rows, response, {
          totalTopLevelRowCount: getTopLevelRowCount({
            tree: response.tree,
            rowCountProp: props.rowCount
          })
        })
      });
    });
    apiRef.current.publishEvent('rowsSet');
    apiRef.current.forceUpdate();
  }, [apiRef, props.rowCount]);
  useGridRegisterPipeApplier(apiRef, 'hydrateRows', applyHydrateRowsProcessor);
  useGridApiMethod(apiRef, rowApi, 'public');
  useGridApiMethod(apiRef, rowProApi, props.signature === GridSignature.DataGrid ? 'private' : 'public');

  /**
   * EFFECTS
   */
  React.useEffect(function () {
    return function () {
      if (timeout.current !== null) {
        clearTimeout(timeout.current);
      }
    };
  }, []);

  // The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridRows`
  // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
  var isFirstRender = React.useRef(true);
  React.useEffect(function () {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }
    var areNewRowsAlreadyInState = apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows;
    var isNewLoadingAlreadyInState = apiRef.current.caches.rows.loadingPropBeforePartialUpdates === props.loading;
    var isNewRowCountAlreadyInState = apiRef.current.caches.rows.rowCountPropBeforePartialUpdates === props.rowCount;

    // The new rows have already been applied (most likely in the `'rowGroupsPreProcessingChange'` listener)
    if (areNewRowsAlreadyInState) {
      // If the loading prop has changed, we need to update its value in the state because it won't be done by `throttledRowsChange`
      if (!isNewLoadingAlreadyInState) {
        apiRef.current.setState(function (state) {
          return _extends({}, state, {
            rows: _extends({}, state.rows, {
              loading: props.loading
            })
          });
        });
        apiRef.current.caches.rows.loadingPropBeforePartialUpdates = props.loading;
        apiRef.current.forceUpdate();
      }
      if (!isNewRowCountAlreadyInState) {
        apiRef.current.setState(function (state) {
          return _extends({}, state, {
            rows: _extends({}, state.rows, {
              totalRowCount: Math.max(props.rowCount || 0, state.rows.totalRowCount),
              totalTopLevelRowCount: Math.max(props.rowCount || 0, state.rows.totalTopLevelRowCount)
            })
          });
        });
        apiRef.current.caches.rows.rowCountPropBeforePartialUpdates = props.rowCount;
        apiRef.current.forceUpdate();
      }
      return;
    }
    logger.debug("Updating all rows, new length ".concat(props.rows.length));
    throttledRowsChange({
      cache: createRowsInternalCache({
        rows: props.rows,
        getRowId: props.getRowId,
        loading: props.loading,
        rowCount: props.rowCount
      }),
      throttle: false
    });
  }, [props.rows, props.rowCount, props.getRowId, props.loading, logger, throttledRowsChange, apiRef]);
};