import React, { HTMLAttributes, useLayoutEffect, useRef, useState } from 'react';
import styles from './silke-virtual.scss';

type SilkeVirtualRowProps<T> = {
  height?: number;
  items: T[];
  indexOffset: number;
  children: (item: T, index: number) => React.ReactNode;
  rowAttrs?: (rowIndex: number) => Omit<HTMLAttributes<HTMLDivElement>, 'children'>;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;

type ObserverListener = [el: HTMLElement, onVisible: (visible: boolean) => void];
const listeners: ObserverListener[] = [];

function findListener(el: HTMLElement): ObserverListener | undefined {
  for (const pair of listeners) {
    if (pair[0] === el) return pair;
  }
}

let observer: IntersectionObserver;

function observe(el: HTMLDivElement, onVisible: (visible: boolean) => void): () => void {
  if (!observer) {
    observer = new IntersectionObserver((e) => {
      for (const t of e) {
        const listener = findListener(t.target as HTMLElement);
        if (listener) listener[1](t.isIntersecting);
      }
    });
  }
  observer.observe(el);
  const pair: ObserverListener = [el, onVisible];
  listeners.push(pair);
  return () => {
    observer.unobserve(el);
    const index = listeners.indexOf(pair);
    if (index !== -1) listeners.splice(index, 1);
  };
}

export function SilkeVirtualRow<T>({
  children,
  rowAttrs,
  items,
  className,
  indexOffset,
  style,
  height,
  ...rest
}: SilkeVirtualRowProps<T>) {
  let cl = styles.row;
  if (className) cl += ' ' + className;
  const ref = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);
  useLayoutEffect(() => {
    const el = ref.current;
    if (el) return observe(el, setVisible);
  }, []);

  if (height) style = { ...style, height };
  if (rowAttrs) Object.assign(rest, rowAttrs(indexOffset));

  return (
    <div ref={ref} style={style} className={cl} {...rest}>
      {visible && items.map((item, i) => children(item, i + indexOffset))}
    </div>
  );
}
