import _extends from '@babel/runtime/helpers/esm/extends';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { useThree, useFrame, context as context$1 } from '@react-three/fiber';
import { easing } from 'maath';

const context = /* @__PURE__ */React.createContext(null);
function useScroll() {
  return React.useContext(context);
}
function ScrollControls({
  eps = 0.00001,
  enabled = true,
  infinite,
  horizontal,
  pages = 1,
  distance = 1,
  damping = 0.25,
  maxSpeed = Infinity,
  prepend = false,
  style = {},
  children
}) {
  const {
    get,
    setEvents,
    gl,
    size,
    invalidate,
    events
  } = useThree();
  const [el] = React.useState(() => document.createElement('div'));
  const [fill] = React.useState(() => document.createElement('div'));
  const [fixed] = React.useState(() => document.createElement('div'));
  const target = gl.domElement.parentNode;
  const scroll = React.useRef(0);
  const state = React.useMemo(() => {
    const state = {
      el,
      eps,
      fill,
      fixed,
      horizontal,
      damping,
      offset: 0,
      delta: 0,
      scroll,
      pages,
      // 0-1 for a range between from -> from + distance
      range(from, distance, margin = 0) {
        const start = from - margin;
        const end = start + distance + margin * 2;
        return this.offset < start ? 0 : this.offset > end ? 1 : (this.offset - start) / (end - start);
      },
      // 0-1-0 for a range between from -> from + distance
      curve(from, distance, margin = 0) {
        return Math.sin(this.range(from, distance, margin) * Math.PI);
      },
      // true/false for a range between from -> from + distance
      visible(from, distance, margin = 0) {
        const start = from - margin;
        const end = start + distance + margin * 2;
        return this.offset >= start && this.offset <= end;
      }
    };
    return state;
  }, [eps, damping, horizontal, pages]);
  React.useEffect(() => {
    el.style.position = 'absolute';
    el.style.width = '100%';
    el.style.height = '100%';
    el.style[horizontal ? 'overflowX' : 'overflowY'] = 'auto';
    el.style[horizontal ? 'overflowY' : 'overflowX'] = 'hidden';
    el.style.top = '0px';
    el.style.left = '0px';
    for (const key in style) {
      el.style[key] = style[key];
    }
    fixed.style.position = 'sticky';
    fixed.style.top = '0px';
    fixed.style.left = '0px';
    fixed.style.width = '100%';
    fixed.style.height = '100%';
    fixed.style.overflow = 'hidden';
    el.appendChild(fixed);
    fill.style.height = horizontal ? '100%' : `${pages * distance * 100}%`;
    fill.style.width = horizontal ? `${pages * distance * 100}%` : '100%';
    fill.style.pointerEvents = 'none';
    el.appendChild(fill);
    if (prepend) target.prepend(el);else target.appendChild(el);

    // Init scroll one pixel in to allow upward/leftward scroll
    el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1;
    const oldTarget = events.connected || gl.domElement;
    requestAnimationFrame(() => events.connect == null ? void 0 : events.connect(el));
    const oldCompute = get().events.compute;
    setEvents({
      compute(event, state) {
        // we are using boundingClientRect because we could not rely on target.offsetTop as canvas could be positioned anywhere in dom
        const {
          left,
          top
        } = target.getBoundingClientRect();
        const offsetX = event.clientX - left;
        const offsetY = event.clientY - top;
        state.pointer.set(offsetX / state.size.width * 2 - 1, -(offsetY / state.size.height) * 2 + 1);
        state.raycaster.setFromCamera(state.pointer, state.camera);
      }
    });
    return () => {
      target.removeChild(el);
      setEvents({
        compute: oldCompute
      });
      events.connect == null || events.connect(oldTarget);
    };
  }, [pages, distance, horizontal, el, fill, fixed, target]);
  React.useEffect(() => {
    if (events.connected === el) {
      const containerLength = size[horizontal ? 'width' : 'height'];
      const scrollLength = el[horizontal ? 'scrollWidth' : 'scrollHeight'];
      const scrollThreshold = scrollLength - containerLength;
      let current = 0;
      let disableScroll = true;
      let firstRun = true;
      const onScroll = () => {
        // Prevent first scroll because it is indirectly caused by the one pixel offset
        if (!enabled || firstRun) return;
        invalidate();
        current = el[horizontal ? 'scrollLeft' : 'scrollTop'];
        scroll.current = current / scrollThreshold;
        if (infinite) {
          if (!disableScroll) {
            if (current >= scrollThreshold) {
              const damp = 1 - state.offset;
              el[horizontal ? 'scrollLeft' : 'scrollTop'] = 1;
              scroll.current = state.offset = -damp;
              disableScroll = true;
            } else if (current <= 0) {
              const damp = 1 + state.offset;
              el[horizontal ? 'scrollLeft' : 'scrollTop'] = scrollLength;
              scroll.current = state.offset = damp;
              disableScroll = true;
            }
          }
          if (disableScroll) setTimeout(() => disableScroll = false, 40);
        }
      };
      el.addEventListener('scroll', onScroll, {
        passive: true
      });
      requestAnimationFrame(() => firstRun = false);
      const onWheel = e => el.scrollLeft += e.deltaY / 2;
      if (horizontal) el.addEventListener('wheel', onWheel, {
        passive: true
      });
      return () => {
        el.removeEventListener('scroll', onScroll);
        if (horizontal) el.removeEventListener('wheel', onWheel);
      };
    }
  }, [el, events, size, infinite, state, invalidate, horizontal, enabled]);
  let last = 0;
  useFrame((_, delta) => {
    last = state.offset;
    easing.damp(state, 'offset', scroll.current, damping, delta, maxSpeed, undefined, eps);
    easing.damp(state, 'delta', Math.abs(last - state.offset), damping, delta, maxSpeed, undefined, eps);
    if (state.delta > eps) invalidate();
  });
  return /*#__PURE__*/React.createElement(context.Provider, {
    value: state
  }, children);
}
const ScrollCanvas = /* @__PURE__ */React.forwardRef(({
  children
}, ref) => {
  const group = React.useRef(null);
  React.useImperativeHandle(ref, () => group.current, []);
  const state = useScroll();
  const {
    width,
    height
  } = useThree(state => state.viewport);
  useFrame(() => {
    group.current.position.x = state.horizontal ? -width * (state.pages - 1) * state.offset : 0;
    group.current.position.y = state.horizontal ? 0 : height * (state.pages - 1) * state.offset;
  });
  return /*#__PURE__*/React.createElement("group", {
    ref: group
  }, children);
});
const ScrollHtml = /*#__PURE__*/React.forwardRef(({
  children,
  style,
  ...props
}, ref) => {
  const state = useScroll();
  const group = React.useRef(null);
  React.useImperativeHandle(ref, () => group.current, []);
  const {
    width,
    height
  } = useThree(state => state.size);
  const fiberState = React.useContext(context$1);
  const root = React.useMemo(() => ReactDOM.createRoot(state.fixed), [state.fixed]);
  useFrame(() => {
    if (state.delta > state.eps) {
      group.current.style.transform = `translate3d(${state.horizontal ? -width * (state.pages - 1) * state.offset : 0}px,${state.horizontal ? 0 : height * (state.pages - 1) * -state.offset}px,0)`;
    }
  });
  root.render(/*#__PURE__*/React.createElement("div", _extends({
    ref: group,
    style: {
      ...style,
      position: 'absolute',
      top: 0,
      left: 0,
      willChange: 'transform'
    }
  }, props), /*#__PURE__*/React.createElement(context.Provider, {
    value: state
  }, /*#__PURE__*/React.createElement(context$1.Provider, {
    value: fiberState
  }, children))));
  return null;
});
const Scroll = /* @__PURE__ */React.forwardRef(({
  html,
  ...props
}, ref) => {
  const El = html ? ScrollHtml : ScrollCanvas;
  return /*#__PURE__*/React.createElement(El, _extends({
    ref: ref
  }, props));
});

export { Scroll, ScrollControls, useScroll };
