/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

import Batchinator from '../Batchinator';
import FillRateHelper from '../FillRateHelper';
import RefreshControl from '../../../exports/RefreshControl';
import ScrollView from '../../../exports/ScrollView';
import StyleSheet from '../../../exports/StyleSheet';
import View from '../../../exports/View';
import ViewabilityHelper from '../ViewabilityHelper';
const flattenStyle = StyleSheet.flatten;
import infoLog from '../infoLog';
import invariant from 'fbjs/lib/invariant';
import { keyExtractor as defaultKeyExtractor, computeWindowedRenderLimits } from '../VirtualizeUtils';
import * as React from 'react';
type ScrollResponderType = any;
import type { ViewProps } from '../../../exports/View';
type ViewStyleProp = $PropertyType<ViewProps, 'style'>;
import type { ViewabilityConfig, ViewToken, ViewabilityConfigCallbackPair } from '../ViewabilityHelper';
import { VirtualizedListCellContextProvider, VirtualizedListContext, VirtualizedListContextProvider, type ChildListState, type ListDebugInfo } from './VirtualizedListContext.js';
type Item = any;
const __DEV__ = process.env.NODE_ENV !== 'production';
const SCROLL_WHEEL_SENSITIVITY = 0.5;
export type Separators = {
  highlight: () => void,
  unhighlight: () => void,
  updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
  ...
};
export type RenderItemProps<ItemT> = {
  item: ItemT,
  index: number,
  separators: Separators,
  ...
};
export type RenderItemType<ItemT> = (info: RenderItemProps<ItemT>) => React.Node;
type ViewabilityHelperCallbackTuple = {
  viewabilityHelper: ViewabilityHelper,
  onViewableItemsChanged: (info: {
    viewableItems: Array<ViewToken>,
    changed: Array<ViewToken>,
    ...
  }) => void,
  ...
};
type RequiredProps = {|
  /**
   * The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override
   * getItem, getItemCount, and keyExtractor to handle any type of index-based data.
   */
  data?: any,
  /**
   * A generic accessor for extracting an item from any sort of data blob.
   */
  getItem: (data: any, index: number) => ?Item,
  /**
   * Determines how many items are in the data blob.
   */
  getItemCount: (data: any) => number,
|};
type OptionalProps = {|
  renderItem?: ?RenderItemType<Item>,
  /**
   * `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
   * implementation, but with a significant perf hit.
   */
  debug?: ?boolean,
  /**
   * DEPRECATED: Virtualization provides significant performance and memory optimizations, but fully
   * unmounts react instances that are outside of the render window. You should only need to disable
   * this for debugging purposes. Defaults to false.
   */
  disableVirtualization?: ?boolean,
  /**
   * A marker property for telling the list to re-render (since it implements `PureComponent`). If
   * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
   * `data` prop, stick it here and treat it immutably.
   */
  extraData?: any,
  // e.g. height, y
  getItemLayout?: (data: any, index: number) => {
    length: number,
    offset: number,
    index: number,
    ...
  },
  horizontal?: ?boolean,
  /**
   * How many items to render in the initial batch. This should be enough to fill the screen but not
   * much more. Note these items will never be unmounted as part of the windowed rendering in order
   * to improve perceived performance of scroll-to-top actions.
   */
  initialNumToRender?: ?number,
  /**
   * Instead of starting at the top with the first item, start at `initialScrollIndex`. This
   * disables the "scroll to top" optimization that keeps the first `initialNumToRender` items
   * always rendered and immediately renders the items starting at this initial index. Requires
   * `getItemLayout` to be implemented.
   */
  initialScrollIndex?: ?number,
  /**
   * Reverses the direction of scroll. Uses scale transforms of -1.
   */
  inverted?: ?boolean,
  keyExtractor?: ?(item: Item, index: number) => string,
  /**
   * Each cell is rendered using this element. Can be a React Component Class,
   * or a render function. Defaults to using View.
   */
  CellRendererComponent?: ?React.ComponentType<any>,
  /**
   * Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
   * `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
   * which will update the `highlighted` prop, but you can also add custom props with
   * `separators.updateProps`.
   */
  ItemSeparatorComponent?: ?React.ComponentType<any>,
  /**
   * Takes an item from `data` and renders it into the list. Example usage:
   *
   *     <FlatList
   *       ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
   *         <View style={[style.separator, highlighted && {marginLeft: 0}]} />
   *       )}
   *       data={[{title: 'Title Text', key: 'item1'}]}
   *       ListItemComponent={({item, separators}) => (
   *         <TouchableHighlight
   *           onPress={() => this._onPress(item)}
   *           onShowUnderlay={separators.highlight}
   *           onHideUnderlay={separators.unhighlight}>
   *           <View style={{backgroundColor: 'white'}}>
   *             <Text>{item.title}</Text>
   *           </View>
   *         </TouchableHighlight>
   *       )}
   *     />
   *
   * Provides additional metadata like `index` if you need it, as well as a more generic
   * `separators.updateProps` function which let's you set whatever props you want to change the
   * rendering of either the leading separator or trailing separator in case the more common
   * `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
   * your use-case.
   */
  ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
  /**
   * Rendered when the list is empty. Can be a React Component Class, a render function, or
   * a rendered element.
   */
  ListEmptyComponent?: ?(React.ComponentType<any> | React.Element<any>),
  /**
   * Rendered at the bottom of all the items. Can be a React Component Class, a render function, or
   * a rendered element.
   */
  ListFooterComponent?: ?(React.ComponentType<any> | React.Element<any>),
  /**
   * Styling for internal View for ListFooterComponent
   */
  ListFooterComponentStyle?: ViewStyleProp,
  /**
   * Rendered at the top of all the items. Can be a React Component Class, a render function, or
   * a rendered element.
   */
  ListHeaderComponent?: ?(React.ComponentType<any> | React.Element<any>),
  /**
   * Styling for internal View for ListHeaderComponent
   */
  ListHeaderComponentStyle?: ViewStyleProp,
  /**
   * A unique identifier for this list. If there are multiple VirtualizedLists at the same level of
   * nesting within another VirtualizedList, this key is necessary for virtualization to
   * work properly.
   */
  listKey?: string,
  /**
   * The maximum number of items to render in each incremental render batch. The more rendered at
   * once, the better the fill rate, but responsiveness may suffer because rendering content may
   * interfere with responding to button taps or other interactions.
   */
  maxToRenderPerBatch?: ?number,
  /**
   * Called once when the scroll position gets within `onStartReachedThreshold` of the rendered
   * content.
   */
  onStartReached?: ?(info: {
    distanceFromStart: number,
    ...
  }) => void,
  /**
   * How far from the start (in units of visible length of the list) the leading edge of the
   * list must be from the start of the content to trigger the `onStartReached` callback.
   * Thus, a value of 0.5 will trigger `onStartReached` when the start of the content is
   * within half the visible length of the list.
   */
  onStartReachedThreshold?: ?number,
  /**
   * Called once when the scroll position gets within `onEndReachedThreshold` of the rendered
   * content.
   */
  onEndReached?: ?(info: {
    distanceFromEnd: number,
    ...
  }) => void,
  /**
   * How far from the end (in units of visible length of the list) the trailing edge of the
   * list must be from the end of the content to trigger the `onEndReached` callback.
   * Thus, a value of 0.5 will trigger `onEndReached` when the end of the content is
   * within half the visible length of the list.
   */
  onEndReachedThreshold?: ?number,
  /**
   * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make
   * sure to also set the `refreshing` prop correctly.
   */
  onRefresh?: ?() => void,
  /**
   * Used to handle failures when scrolling to an index that has not been measured yet. Recommended
   * action is to either compute your own offset and `scrollTo` it, or scroll as far as possible and
   * then try again after more items have been rendered.
   */
  onScrollToIndexFailed?: ?(info: {
    index: number,
    highestMeasuredFrameIndex: number,
    averageItemLength: number,
    ...
  }) => void,
  /**
   * Called when the viewability of rows changes, as defined by the
   * `viewabilityConfig` prop.
   */
  onViewableItemsChanged?: ?(info: {
    viewableItems: Array<ViewToken>,
    changed: Array<ViewToken>,
    ...
  }) => void,
  persistentScrollbar?: ?boolean,
  /**
   * Set this when offset is needed for the loading indicator to show correctly.
   */
  progressViewOffset?: number,
  /**
   * A custom refresh control element. When set, it overrides the default
   * <RefreshControl> component built internally. The onRefresh and refreshing
   * props are also ignored. Only works for vertical VirtualizedList.
   */
  refreshControl?: ?React.Element<any>,
  /**
   * Set this true while waiting for new data from a refresh.
   */
  refreshing?: ?boolean,
  /**
   * Note: may have bugs (missing content) in some circumstances - use at your own risk.
   *
   * This may improve scroll performance for large lists.
   */
  removeClippedSubviews?: boolean,
  /**
   * Render a custom scroll component, e.g. with a differently styled `RefreshControl`.
   */
  renderScrollComponent?: (props: Object) => React.Element<any>,
  /**
   * Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off
   * screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`.
   */
  updateCellsBatchingPeriod?: ?number,
  /**
   * See `ViewabilityHelper` for flow type and further documentation.
   */
  viewabilityConfig?: ViewabilityConfig,
  /**
   * List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged
   * will be called when its corresponding ViewabilityConfig's conditions are met.
   */
  viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>,
  /**
   * Determines the maximum number of items rendered outside of the visible area, in units of
   * visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will
   * render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing
   * this number will reduce memory consumption and may improve performance, but will increase the
   * chance that fast scrolling may reveal momentary blank areas of unrendered content.
   */
  windowSize?: ?number,
  /**
   * The legacy implementation is no longer supported.
   */
  legacyImplementation?: empty,
|};
type Props = {|
  ...React.ElementConfig<typeof ScrollView>,
  ...RequiredProps,
  ...OptionalProps,
|};
let _usedIndexForKey = false;
let _keylessItemComponentName: string = '';
type State = {
  first: number,
  last: number,
  // Used to track items added at the start of the list for maintainVisibleContentPosition.
  firstItemKey: ?string,
  // When using maintainVisibleContentPosition we need to adjust the window to make sure
  // make sure that the visible elements are still rendered.
  maintainVisibleContentPositionAdjustment: ?number,
};

/**
 * Default Props Helper Functions
 * Use the following helper functions for default values
 */

// horizontalOrDefault(this.props.horizontal)
declare function horizontalOrDefault(horizontal: ?boolean): any; // initialNumToRenderOrDefault(this.props.initialNumToRenderOrDefault)
declare function initialNumToRenderOrDefault(initialNumToRender: ?number): any; // maxToRenderPerBatchOrDefault(this.props.maxToRenderPerBatch)
declare function maxToRenderPerBatchOrDefault(maxToRenderPerBatch: ?number): any; // onStartReachedThresholdOrDefault(this.props.onStartReachedThreshold)
declare function onStartReachedThresholdOrDefault(onStartReachedThreshold: ?number): any; // onEndReachedThresholdOrDefault(this.props.onEndReachedThreshold)
declare function onEndReachedThresholdOrDefault(onEndReachedThreshold: ?number): any; // getScrollingThreshold(visibleLength, onEndReachedThreshold)
declare function getScrollingThreshold(threshold: number, visibleLength: number): any; // scrollEventThrottleOrDefault(this.props.scrollEventThrottle)
declare function scrollEventThrottleOrDefault(scrollEventThrottle: ?number): any; // windowSizeOrDefault(this.props.windowSize)
declare function windowSizeOrDefault(windowSize: ?number): any;
declare function extractKey(props: Props, item: Item, index: number): string;
declare function findItemIndexWithKey(props: Props, key: string): ?number;
declare function getItemKey(props: Props, index: number): ?string;
/**
 * Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist)
 * and [`<SectionList>`](https://reactnative.dev/docs/sectionlist) components, which are also better
 * documented. In general, this should only really be used if you need more flexibility than
 * `FlatList` provides, e.g. for use with immutable data instead of plain arrays.
 *
 * Virtualization massively improves memory consumption and performance of large lists by
 * maintaining a finite render window of active items and replacing all items outside of the render
 * window with appropriately sized blank space. The window adapts to scrolling behavior, and items
 * are rendered incrementally with low-pri (after any running interactions) if they are far from the
 * visible area, or with hi-pri otherwise to minimize the potential of seeing blank space.
 *
 * Some caveats:
 *
 * - Internal state is not preserved when content scrolls out of the render window. Make sure all
 *   your data is captured in the item data or external stores like Flux, Redux, or Relay.
 * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
 *   equal. Make sure that everything your `renderItem` function depends on is passed as a prop
 *   (e.g. `extraData`) that is not `===` after updates, otherwise your UI may not update on
 *   changes. This includes the `data` prop and parent component state.
 * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
 *   offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see
 *   blank content. This is a tradeoff that can be adjusted to suit the needs of each application,
 *   and we are working on improving it behind the scenes.
 * - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key.
 *   Alternatively, you can provide a custom `keyExtractor` prop.
 * - As an effort to remove defaultProps, use helper functions when referencing certain props
 *
 */
declare class VirtualizedList extends React.PureComponent<Props, State> {
  static contextType: typeof VirtualizedListContext,
  pushOrUnshift(input: Array<any>, item: Item): any,
  scrollToEnd(params?: ?{
    animated?: ?boolean,
    ...
  }): any,
  scrollToIndex(params: {
    animated?: ?boolean,
    index: number,
    viewOffset?: number,
    viewPosition?: number,
    ...
  }): any,
  scrollToItem(params: {
    animated?: ?boolean,
    item: Item,
    viewPosition?: number,
    ...
  }): any,
  scrollToOffset(params: {
    animated?: ?boolean,
    offset: number,
    ...
  }): any,
  recordInteraction(): any,
  flashScrollIndicators(): any,
  getScrollResponder(): ?ScrollResponderType,
  getScrollableNode(): ?number,
  getScrollRef(): ?React.ElementRef<typeof ScrollView> | ?React.ElementRef<typeof View>,
  _getCellKey(): string,
  _getListKey(): string,
  _getDebugInfo(): ListDebugInfo,
  _getScrollMetrics: any,
  hasMore(): boolean,
  _getOutermostParentListRef: any,
  _getNestedChildState: any,
  _registerAsNestedChild: any,
  _unregisterAsNestedChild: any,
  state: State,
  invertedWheelEventHandler: ?(ev: any) => void,
  constructor(props: Props): any,
  componentDidMount(): any,
  componentWillUnmount(): any,
  setupWebWheelHandler(): any,
  teardownWebWheelHandler(): any,
  static getDerivedStateFromProps(newProps: Props, prevState: State): State,
  _pushCells(cells: Array<Object>, stickyHeaderIndices: Array<number>, stickyIndicesFromProps: Set<number>, first: number, last: number, inversionStyle: ViewStyleProp): any,
  _onUpdateSeparators: any,
  _isVirtualizationDisabled(): boolean,
  _isNestedWithSameOrientation(): boolean,
  _getSpacerKey: any,
  render(): React.Node,
  componentDidUpdate(prevProps: Props): any,
  _averageCellLength: any,
  _cellKeysToChildListKeys: Map<string, Set<string>>,
  _cellRefs: any,
  _fillRateHelper: FillRateHelper,
  _frames: any,
  _footerLength: any,
  _hasDoneInitialScroll: any,
  _hasInteracted: any,
  _hasMore: any,
  _hasWarned: any,
  _headerLength: any,
  _hiPriInProgress: boolean,
  _highestMeasuredFrameIndex: any,
  _indicesToKeys: Map<number, string>,
  _nestedChildLists: Map<string, {
    ref: ?VirtualizedList,
    state: ?ChildListState,
    ...
  }>,
  _offsetFromParentVirtualizedList: number,
  _prevParentOffset: number,
  _scrollMetrics: any,
  _scrollRef: ?React.ElementRef<any>,
  _sentStartForContentLength: any,
  _sentEndForContentLength: any,
  _totalCellLength: any,
  _totalCellsMeasured: any,
  _updateCellsToRenderBatcher: Batchinator,
  _viewabilityTuples: Array<ViewabilityHelperCallbackTuple>,
  _captureScrollRef: any,
  _computeBlankness(): any,
  _defaultRenderScrollComponent: any,
  _onCellLayout(e: any, cellKey: any, index: any): any,
  _onCellUnmount: any,
  _triggerRemeasureForChildListsInCell(cellKey: string): void,
  measureLayoutRelativeToContainingList(): void,
  _onLayout: any,
  _onLayoutEmpty: any,
  _getFooterCellKey(): string,
  _onLayoutFooter: any,
  _onLayoutHeader: any,
  _renderDebugOverlay(): any,
  _selectLength(metrics: $ReadOnly<{
    height: number,
    width: number,
    ...
  }>): number,
  _selectOffset(metrics: $ReadOnly<{
    x: number,
    y: number,
    ...
  }>): number,
  _maybeCallOnEdgeReached(): any,
  _onContentSizeChange: any,
  _convertParentScrollMetrics: any,
  _onScroll: any,
  _scheduleCellsToRenderUpdate(): any,
  _onScrollBeginDrag: any,
  _onScrollEndDrag: any,
  _onMomentumScrollBegin: any,
  _onMomentumScrollEnd: any,
  _updateCellsToRender: any,
  _createViewToken: any,
  _getFrameMetricsApprox: any,
  _getFrameMetrics: any,
  _updateViewableItems(data: any): any,
}
type CellRendererProps = {
  CellRendererComponent?: ?React.ComponentType<any>,
  ItemSeparatorComponent: ?React.ComponentType<any | {
    highlighted: boolean,
    leadingItem: ?Item,
  }>,
  cellKey: string,
  fillRateHelper: FillRateHelper,
  horizontal: ?boolean,
  index: number,
  inversionStyle: ViewStyleProp,
  item: Item,
  // This is extracted by ScrollViewStickyHeader
  onLayout: (event: Object) => void,
  onUnmount: (cellKey: string) => void,
  onUpdateSeparators: (cellKeys: Array<?string>, props: Object) => void,
  parentProps: {
    // e.g. height, y,
    getItemLayout?: (data: any, index: number) => {
      length: number,
      offset: number,
      index: number,
      ...
    },
    renderItem?: ?RenderItemType<Item>,
    ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
    ...
  },
  prevCellKey: ?string,
  ...
};
type CellRendererState = {
  separatorProps: $ReadOnly<{|
    highlighted: boolean,
    leadingItem: ?Item,
  |}>,
  ...
};
declare class CellRenderer extends React.Component<CellRendererProps, CellRendererState> {
  state: any,
  static getDerivedStateFromProps(props: CellRendererProps, prevState: CellRendererState): ?CellRendererState,
  _separators: any,
  updateSeparatorProps(newProps: Object): any,
  componentWillUnmount(): any,
  _renderElement(renderItem: any, ListItemComponent: any, item: any, index: any): any,
  render(): any,
}
declare function describeNestedLists(childList: {
  +cellKey: string,
  +key: string,
  +ref: VirtualizedList,
  +parentDebugInfo: ListDebugInfo,
  +horizontal: boolean,
  ...
}): any;
const styles = StyleSheet.create({
  verticallyInverted: {
    transform: 'scaleY(-1)'
  },
  horizontallyInverted: {
    transform: 'scaleX(-1)'
  },
  row: {
    flexDirection: 'row'
  },
  rowReverse: {
    flexDirection: 'row-reverse'
  },
  columnReverse: {
    flexDirection: 'column-reverse'
  },
  debug: {
    flex: 1
  },
  debugOverlayBase: {
    position: 'absolute',
    top: 0,
    right: 0
  },
  debugOverlay: {
    bottom: 0,
    width: 20,
    borderColor: 'blue',
    borderWidth: 1
  },
  debugOverlayFrame: {
    left: 0,
    backgroundColor: 'orange'
  },
  debugOverlayFrameLast: {
    left: 0,
    borderColor: 'green',
    borderWidth: 2
  },
  debugOverlayFrameVis: {
    left: 0,
    borderColor: 'red',
    borderWidth: 2
  }
});
export default VirtualizedList;