"use client";
import {
  useStoreState
} from "./Q5W46E73.js";
import {
  useCollectionContext
} from "./SMPCIMZM.js";
import {
  createElement,
  forwardRef
} from "./GWSL6KNJ.js";
import {
  useBooleanEvent,
  useEvent,
  useForceUpdate,
  useId,
  useMergeRefs,
  useWrapElement
} from "./KPHZR4MB.js";

// src/collection/collection-renderer.tsx
import { getScrollingElement, getWindow } from "@ariakit/core/utils/dom";
import { invariant, shallowEqual } from "@ariakit/core/utils/misc";
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState
} from "react";
import { flushSync } from "react-dom";
import { jsx } from "react/jsx-runtime";
var TagName = "div";
var CollectionRendererContext = createContext(null);
function createTask() {
  let raf = 0;
  const run = (cb) => {
    if (raf) return;
    raf = requestAnimationFrame(() => {
      raf = 0;
      cb();
    });
  };
  const cancel = () => {
    cancelAnimationFrame(raf);
    raf = 0;
  };
  return { run, cancel };
}
function findNearestIndex(items, target, getValue) {
  let left = 0;
  let right = getItemsLength(items) - 1;
  while (left <= right) {
    const index = (left + right) / 2 | 0;
    const value = getValue(index);
    if (value === target) return index;
    else if (value < target) left = index + 1;
    else right = index - 1;
  }
  if (left > 0) return left - 1;
  return 0;
}
function getItemsLength(items) {
  return typeof items === "number" ? items : items.length;
}
function getItemObject(item) {
  if (!item || typeof item !== "object") {
    return { value: item };
  }
  return item;
}
function getItemId(item, index, baseId) {
  var _a;
  invariant(baseId, "CollectionRenderer must be given an `id` prop.");
  const defaultId = `${baseId}/${index}`;
  return (_a = getItemObject(item).id) != null ? _a : defaultId;
}
function getItem(items, index) {
  if (typeof items === "number") {
    if (index >= items) return null;
    return {};
  }
  const item = items[index];
  if (!item) return null;
  if (typeof item === "object") return item;
  return { value: item };
}
function getItemSize(item, horizontal, fallbackElement) {
  var _a, _b, _c, _d, _e;
  const itemObject = getItemObject(item);
  horizontal = itemObject.orientation === "horizontal" || horizontal;
  const prop = horizontal ? "width" : "height";
  const style = itemObject.style;
  if (style) {
    const size = style[prop];
    if (typeof size === "number") return size;
  }
  const items = itemObject.items;
  if (items == null ? void 0 : items.length) {
    const hasSameOrientation = !itemObject.orientation || horizontal && itemObject.orientation === "horizontal" || !horizontal && itemObject.orientation === "vertical";
    const paddingStart = (_b = (_a = itemObject.paddingStart) != null ? _a : itemObject.padding) != null ? _b : 0;
    const paddingEnd = (_d = (_c = itemObject.paddingEnd) != null ? _c : itemObject.padding) != null ? _d : 0;
    const padding = hasSameOrientation ? paddingStart + paddingEnd : 0;
    const initialSize = ((_e = itemObject.gap) != null ? _e : 0) * (items.length - 1) + padding;
    if (hasSameOrientation && itemObject.itemSize) {
      return initialSize + itemObject.itemSize * items.length;
    }
    const totalSize = items.reduce(
      (sum, item2) => sum + getItemSize(item2, horizontal),
      initialSize
    );
    if (totalSize !== initialSize) return totalSize;
  }
  const element = fallbackElement !== false ? itemObject.element || fallbackElement : null;
  if (element == null ? void 0 : element.isConnected) {
    return element.getBoundingClientRect()[prop];
  }
  return 0;
}
function getAverageSize(props) {
  const length = getItemsLength(props.items);
  let currentIndex = 0;
  let averageSize = props.estimatedItemSize;
  const setAverageSize = (size) => {
    const prevIndex = currentIndex;
    currentIndex = currentIndex + 1;
    averageSize = (averageSize * prevIndex + size) / currentIndex;
  };
  for (let index = 0; index < length; index += 1) {
    const item = getItem(props.items, index);
    const itemId = getItemId(item, index, props.baseId);
    const itemData = props.data.get(itemId);
    const fallbackElement = props.elements.get(itemId);
    const size = getItemSize(item, props.horizontal, fallbackElement);
    if (size) {
      setAverageSize(size);
    } else if (itemData == null ? void 0 : itemData.rendered) {
      setAverageSize(itemData.end - itemData.start);
    }
  }
  return averageSize;
}
function getScrollOffset(scroller, horizontal) {
  if ("scrollX" in scroller) {
    return horizontal ? scroller.scrollX : scroller.scrollY;
  }
  return horizontal ? scroller.scrollLeft : scroller.scrollTop;
}
function getViewport(scroller) {
  const { defaultView, documentElement } = scroller.ownerDocument;
  if (scroller === documentElement) return defaultView;
  return scroller;
}
function useScroller(rendererRef) {
  const [scroller, setScroller] = useState(null);
  useEffect(() => {
    const renderer = rendererRef == null ? void 0 : rendererRef.current;
    if (!renderer) return;
    const scroller2 = getScrollingElement(renderer);
    if (!scroller2) return;
    setScroller(scroller2);
  }, [rendererRef]);
  return scroller;
}
function getRendererOffset(renderer, scroller, horizontal) {
  const win = getWindow(renderer);
  const htmlElement = win == null ? void 0 : win.document.documentElement;
  const rendererRect = renderer.getBoundingClientRect();
  const rendererOffset = horizontal ? rendererRect.left : rendererRect.top;
  if (scroller === htmlElement) {
    const scrollOffset2 = getScrollOffset(win, horizontal);
    return scrollOffset2 + rendererOffset;
  }
  const scrollerRect = scroller.getBoundingClientRect();
  const scrollerOffset = horizontal ? scrollerRect.left : scrollerRect.top;
  const scrollOffset = getScrollOffset(scroller, horizontal);
  return rendererOffset - scrollerOffset + scrollOffset;
}
function getOffsets(renderer, scroller, horizontal) {
  const scrollOffset = getScrollOffset(scroller, horizontal);
  const rendererOffset = getRendererOffset(renderer, scroller, horizontal);
  const scrollSize = horizontal ? scroller.clientWidth : scroller.clientHeight;
  const start = scrollOffset - rendererOffset;
  const end = start + scrollSize;
  return { start, end };
}
function getItemsEnd(props) {
  const length = getItemsLength(props.items);
  const totalPadding = props.paddingStart + props.paddingEnd;
  if (!length) return totalPadding;
  const lastIndex = length - 1;
  const totalGap = lastIndex * props.gap;
  if (props.itemSize != null) {
    return length * props.itemSize + totalGap + totalPadding;
  }
  const defaultEnd = length * props.estimatedItemSize + totalGap + totalPadding;
  if (!props.baseId) return defaultEnd;
  const lastItem = getItem(props.items, lastIndex);
  const lastItemId = getItemId(lastItem, lastIndex, props.baseId);
  const lastItemData = props.data.get(lastItemId);
  if (lastItemData == null ? void 0 : lastItemData.end) return lastItemData.end + props.paddingEnd;
  if (!Array.isArray(props.items)) return defaultEnd;
  const end = props.items.reduce(
    (sum, item) => sum + getItemSize(item, props.horizontal, false),
    0
  );
  if (!end) return defaultEnd;
  return end + totalGap + totalPadding;
}
function getData(props) {
  var _a;
  const length = getItemsLength(props.items);
  let nextData;
  let start = props.paddingStart;
  const avgSize = getAverageSize(props);
  for (let index = 0; index < length; index += 1) {
    const item = getItem(props.items, index);
    const itemId = getItemId(item, index, props.baseId);
    const itemData = props.data.get(itemId);
    const prevRendered = (_a = itemData == null ? void 0 : itemData.rendered) != null ? _a : false;
    const setSize = (size2, rendered = prevRendered) => {
      start = start ? start + props.gap : start;
      const end = start + size2;
      const nextItemData = { index, rendered, start, end };
      if (!shallowEqual(itemData, nextItemData)) {
        if (!nextData) {
          nextData = new Map(props.data);
        }
        nextData.set(itemId, { index, rendered, start, end });
      }
      start = end;
    };
    const size = getItemSize(
      item,
      props.horizontal,
      props.elements.get(itemId)
    );
    if (size) {
      setSize(size, true);
    } else if (itemData == null ? void 0 : itemData.rendered) {
      setSize(itemData.end - itemData.start, true);
    } else {
      setSize(avgSize);
    }
  }
  return nextData;
}
function useCollectionRenderer({
  store,
  items: itemsProp,
  initialItems = 0,
  gap = 0,
  itemSize,
  estimatedItemSize = 40,
  overscan: overscanProp,
  orientation: orientationProp,
  padding = 0,
  paddingStart = padding,
  paddingEnd = padding,
  persistentIndices,
  renderOnScroll = true,
  renderOnResize = !!renderOnScroll,
  children: renderItem,
  ...props
}) {
  var _a, _b;
  const context = useCollectionContext();
  store = store || context;
  const items = useStoreState(
    store,
    (state) => itemsProp != null ? itemsProp : state == null ? void 0 : state.items
  );
  invariant(
    items != null,
    process.env.NODE_ENV !== "production" && "CollectionRenderer must be either wrapped in a Collection component or be given an `items` prop."
  );
  let parent = useContext(CollectionRendererContext);
  if (store && (parent == null ? void 0 : parent.store) !== store) {
    parent = null;
  }
  const parentData = parent == null ? void 0 : parent.childrenData;
  const orientation = (_a = orientationProp != null ? orientationProp : parent == null ? void 0 : parent.orientation) != null ? _a : "vertical";
  const overscan = (_b = overscanProp != null ? overscanProp : parent == null ? void 0 : parent.overscan) != null ? _b : 1;
  const ref = useRef(null);
  const baseId = useId(props.id);
  const horizontal = orientation === "horizontal";
  const elements = useMemo(() => /* @__PURE__ */ new Map(), []);
  const [elementsUpdated, updateElements] = useForceUpdate();
  const [defaultVisibleIndices, setVisibleIndices] = useState(() => {
    if (!initialItems) return [];
    const length = getItemsLength(items);
    const initialLength = Math.min(length, Math.abs(initialItems));
    return Array.from({ length: initialLength }, (_, index) => {
      if (initialItems < 0) return length - index - 1;
      return index;
    });
  });
  const visibleIndices = useMemo(() => {
    if (!persistentIndices) return defaultVisibleIndices;
    const nextIndices = defaultVisibleIndices.slice();
    for (const index of persistentIndices) {
      if (index < 0) continue;
      if (nextIndices.includes(index)) continue;
      nextIndices.push(index);
    }
    nextIndices.sort((a, b) => a - b);
    if (shallowEqual(defaultVisibleIndices, nextIndices)) {
      return defaultVisibleIndices;
    }
    return nextIndices;
  }, [defaultVisibleIndices, persistentIndices]);
  const [data, setData] = useState(() => {
    if (!baseId) return /* @__PURE__ */ new Map();
    const data2 = (parentData == null ? void 0 : parentData.get(baseId)) || /* @__PURE__ */ new Map();
    if (itemSize != null) return data2;
    if (!items) return data2;
    const nextData = getData({
      baseId,
      items,
      data: data2,
      gap,
      elements,
      horizontal,
      paddingStart,
      itemSize,
      estimatedItemSize
    });
    return nextData || data2;
  });
  const totalSize = useMemo(() => {
    return getItemsEnd({
      baseId,
      items,
      data,
      gap,
      horizontal,
      itemSize,
      estimatedItemSize,
      paddingStart,
      paddingEnd
    });
  }, [
    baseId,
    items,
    data,
    gap,
    horizontal,
    itemSize,
    estimatedItemSize,
    paddingStart,
    paddingEnd
  ]);
  useEffect(() => {
    if (!baseId) return;
    parentData == null ? void 0 : parentData.set(baseId, data);
  }, [baseId, parentData, data]);
  useEffect(() => {
    if (itemSize != null) return;
    if (!baseId) return;
    if (!items) return;
    const nextData = getData({
      baseId,
      items,
      data,
      gap,
      elements,
      horizontal,
      paddingStart,
      itemSize,
      estimatedItemSize
    });
    if (nextData) {
      setData(nextData);
    }
  }, [
    elementsUpdated,
    itemSize,
    baseId,
    items,
    data,
    gap,
    elements,
    horizontal,
    paddingStart,
    estimatedItemSize
  ]);
  const scroller = useScroller(items ? ref : null);
  const offsetsRef = useRef({ start: 0, end: 0 });
  const processVisibleIndices = useCallback(() => {
    const offsets = offsetsRef.current;
    if (!items) return;
    if (!baseId) return;
    if (!offsets.end) return;
    if (!data.size && !itemSize) return;
    const length = getItemsLength(items);
    const getItemOffset = (index, prop = "start") => {
      var _a2;
      if (itemSize) {
        const start2 = itemSize * index + gap * index + paddingStart;
        if (prop === "start") return start2;
        return start2 + itemSize;
      }
      const item = getItem(items, index);
      const itemId = getItemId(item, index, baseId);
      const itemData = data.get(itemId);
      return (_a2 = itemData == null ? void 0 : itemData[prop]) != null ? _a2 : 0;
    };
    const initialStart = findNearestIndex(items, offsets.start, getItemOffset);
    let initialEnd = initialStart;
    while (initialEnd < length && getItemOffset(initialEnd) < offsets.end) {
      initialEnd += 1;
    }
    const finalOverscan = initialEnd - initialStart ? overscan : 0;
    const start = Math.max(initialStart - finalOverscan, 0);
    const end = Math.min(initialEnd + finalOverscan, length);
    const indices = Array.from(
      { length: end - start },
      (_, index) => index + start
    );
    setVisibleIndices((prevIndices) => {
      if (shallowEqual(prevIndices, indices)) return prevIndices;
      return indices;
    });
  }, [
    elementsUpdated,
    items,
    baseId,
    data,
    itemSize,
    gap,
    paddingStart,
    overscan
  ]);
  useEffect(processVisibleIndices, [processVisibleIndices]);
  const processVisibleIndicesEvent = useEvent(processVisibleIndices);
  useEffect(() => {
    const renderer = ref.current;
    if (!renderer) return;
    if (!scroller) return;
    offsetsRef.current = getOffsets(renderer, scroller, horizontal);
    processVisibleIndicesEvent();
  }, [scroller, horizontal, processVisibleIndicesEvent]);
  const mayRenderOnScroll = !!renderOnScroll;
  const renderOnScrollProp = useBooleanEvent(renderOnScroll);
  useEffect(() => {
    if (!mayRenderOnScroll) return;
    const renderer = ref.current;
    if (!renderer) return;
    if (!scroller) return;
    const viewport = getViewport(scroller);
    if (!viewport) return;
    const task = createTask();
    const onScroll = (event) => {
      task.run(() => {
        if (!renderOnScrollProp(event)) return;
        offsetsRef.current = getOffsets(renderer, scroller, horizontal);
        processVisibleIndicesEvent();
      });
    };
    viewport.addEventListener("scroll", onScroll, { passive: true });
    return () => {
      task.cancel();
      viewport.removeEventListener("scroll", onScroll);
    };
  }, [
    mayRenderOnScroll,
    scroller,
    renderOnScrollProp,
    horizontal,
    processVisibleIndicesEvent
  ]);
  const mayRenderOnResize = !!renderOnResize;
  const renderOnResizeProp = useBooleanEvent(renderOnResize);
  useEffect(() => {
    if (!mayRenderOnResize) return;
    const renderer = ref.current;
    if (!renderer) return;
    if (!scroller) return;
    const viewport = getViewport(scroller);
    if (!viewport) return;
    const task = createTask();
    if (viewport === scroller) {
      if (typeof ResizeObserver !== "function") return;
      let firstRun = true;
      const observer = new ResizeObserver(() => {
        if (firstRun) {
          firstRun = false;
          return;
        }
        task.run(() => {
          if (!renderOnResizeProp(scroller)) return;
          offsetsRef.current = getOffsets(renderer, scroller, horizontal);
          processVisibleIndicesEvent();
        });
      });
      observer.observe(scroller);
      return () => {
        task.cancel();
        observer.disconnect();
      };
    }
    const onResize = () => {
      task.run(() => {
        if (!renderOnResizeProp(scroller)) return;
        offsetsRef.current = getOffsets(renderer, scroller, horizontal);
        processVisibleIndicesEvent();
      });
    };
    viewport.addEventListener("resize", onResize, { passive: true });
    return () => {
      task.cancel();
      viewport.removeEventListener("resize", onResize);
    };
  }, [
    mayRenderOnResize,
    scroller,
    renderOnResizeProp,
    horizontal,
    processVisibleIndicesEvent
  ]);
  useEffect(() => {
    if (typeof IntersectionObserver !== "function") return;
    const renderer = ref.current;
    if (!renderer) return;
    if (!scroller) return;
    const viewport = getViewport(scroller);
    if (!viewport) return;
    const observer = new IntersectionObserver(
      () => {
        offsetsRef.current = getOffsets(renderer, scroller, horizontal);
        processVisibleIndicesEvent();
      },
      { root: scroller === viewport ? scroller : null }
    );
    observer.observe(renderer);
    return () => {
      observer.disconnect();
    };
  }, [scroller, processVisibleIndicesEvent]);
  const elementObserver = useMemo(() => {
    if (typeof ResizeObserver !== "function") return;
    return new ResizeObserver(() => {
      flushSync(updateElements);
    });
  }, [updateElements]);
  const itemRef = useCallback(
    (element) => {
      if (!element) return;
      if (itemSize) return;
      updateElements();
      elements.set(element.id, element);
      elementObserver == null ? void 0 : elementObserver.observe(element);
    },
    [itemSize, elements, updateElements, elementObserver]
  );
  const getItemProps = useCallback(
    (item, index) => {
      var _a2, _b2;
      const itemId = getItemId(item, index, baseId);
      const offset = itemSize ? paddingStart + itemSize * index + gap * index : (_b2 = (_a2 = data.get(itemId)) == null ? void 0 : _a2.start) != null ? _b2 : 0;
      const baseItemProps = {
        id: itemId,
        ref: itemRef,
        index,
        style: {
          position: "absolute",
          left: horizontal ? offset : 0,
          top: horizontal ? 0 : offset
        }
      };
      if (itemSize) {
        baseItemProps.style[horizontal ? "width" : "height"] = itemSize;
      }
      if (item == null) return baseItemProps;
      const itemProps = getItemObject(item);
      return {
        ...itemProps,
        ...baseItemProps,
        style: {
          ...itemProps.style,
          ...baseItemProps.style
        }
      };
    },
    [baseId, data, itemSize, paddingStart, gap, horizontal, itemRef]
  );
  const itemsProps = useMemo(() => {
    return visibleIndices.map((index) => {
      if (index < 0) return;
      const item = getItem(items, index);
      if (!item) return;
      return getItemProps(item, index);
    }).filter((value) => value != null);
  }, [items, visibleIndices, getItemProps]);
  const children = itemsProps == null ? void 0 : itemsProps.map((itemProps) => {
    return renderItem == null ? void 0 : renderItem(itemProps);
  });
  const styleProp = props.style;
  const sizeProperty = horizontal ? "width" : "height";
  const style = useMemo(
    () => ({
      flex: "none",
      position: "relative",
      [sizeProperty]: totalSize,
      ...styleProp
    }),
    [styleProp, sizeProperty, totalSize]
  );
  const childrenData = useMemo(() => /* @__PURE__ */ new Map(), []);
  const providerValue = useMemo(
    () => ({ store, orientation, overscan, childrenData }),
    [store, orientation, overscan, childrenData]
  );
  props = useWrapElement(
    props,
    (element) => /* @__PURE__ */ jsx(CollectionRendererContext.Provider, { value: providerValue, children: element }),
    [providerValue]
  );
  props = {
    id: baseId,
    ...props,
    style,
    ref: useMergeRefs(ref, props.ref)
  };
  return { ...props, children };
}
var CollectionRenderer = forwardRef(function CollectionRenderer2(props) {
  const htmlProps = useCollectionRenderer(props);
  return createElement(TagName, htmlProps);
});
var getCollectionRendererItem = getItem;
var getCollectionRendererItemId = getItemId;

export {
  useCollectionRenderer,
  CollectionRenderer,
  getCollectionRendererItem,
  getCollectionRendererItemId
};
