'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var events = require('../../dist/events-d0566a2e.cjs.dev.js');
var React = require('react');
var THREE = require('three');
var reactNative = require('react-native');
var expoGl = require('expo-gl');
var itsFine = require('its-fine');
var jsxRuntime = require('react/jsx-runtime');
var expoAsset = require('expo-asset');
var fs = require('expo-file-system');
var base64Js = require('base64-js');
var buffer = require('buffer');
require('react-reconciler/constants');
require('zustand');
require('suspend-react');
require('react-reconciler');
require('scheduler');

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n["default"] = e;
  return Object.freeze(n);
}

var React__namespace = /*#__PURE__*/_interopNamespace(React);
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);

/**
 * A native canvas which accepts threejs elements as children.
 * @see https://docs.pmnd.rs/react-three-fiber/api/canvas
 */
const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
  children,
  style,
  gl,
  events: events$1 = events.createPointerEvents,
  shadows,
  linear,
  flat,
  legacy,
  orthographic,
  frameloop,
  performance,
  raycaster,
  camera,
  scene,
  onPointerMissed,
  onCreated,
  ...props
}, forwardedRef) => {
  // Create a known catalogue of Threejs-native elements
  // This will include the entire THREE namespace by default, users can extend
  // their own elements by using the createRoot API instead
  React__namespace.useMemo(() => events.extend(THREE__namespace), []);
  const Bridge = itsFine.useContextBridge();
  const [{
    width,
    height,
    top,
    left
  }, setSize] = React__namespace.useState({
    width: 0,
    height: 0,
    top: 0,
    left: 0
  });
  const [canvas, setCanvas] = React__namespace.useState(null);
  const [bind, setBind] = React__namespace.useState();
  React__namespace.useImperativeHandle(forwardedRef, () => viewRef.current);
  const handlePointerMissed = events.useMutableCallback(onPointerMissed);
  const [block, setBlock] = React__namespace.useState(false);
  const [error, setError] = React__namespace.useState(undefined);

  // Suspend this component if block is a promise (2nd run)
  if (block) throw block;
  // Throw exception outwards if anything within canvas throws
  if (error) throw error;
  const viewRef = React__namespace.useRef(null);
  const root = React__namespace.useRef(null);
  const [antialias, setAntialias] = React__namespace.useState(true);
  const onLayout = React__namespace.useCallback(e => {
    const {
      width,
      height,
      x,
      y
    } = e.nativeEvent.layout;
    setSize({
      width,
      height,
      top: y,
      left: x
    });
  }, []);

  // Called on context create or swap
  // https://github.com/pmndrs/react-three-fiber/pull/2297
  const onContextCreate = React__namespace.useCallback(context => {
    const listeners = new Map();
    const canvas = {
      style: {},
      width: context.drawingBufferWidth,
      height: context.drawingBufferHeight,
      clientWidth: context.drawingBufferWidth,
      clientHeight: context.drawingBufferHeight,
      getContext: (_, {
        antialias = false
      }) => {
        setAntialias(antialias);
        return context;
      },
      addEventListener(type, listener) {
        let callbacks = listeners.get(type);
        if (!callbacks) {
          callbacks = [];
          listeners.set(type, callbacks);
        }
        callbacks.push(listener);
      },
      removeEventListener(type, listener) {
        const callbacks = listeners.get(type);
        if (callbacks) {
          const index = callbacks.indexOf(listener);
          if (index !== -1) callbacks.splice(index, 1);
        }
      },
      dispatchEvent(event) {
        Object.assign(event, {
          target: this
        });
        const callbacks = listeners.get(event.type);
        if (callbacks) {
          for (const callback of callbacks) {
            callback(event);
          }
        }
      },
      setPointerCapture() {
        // TODO
      },
      releasePointerCapture() {
        // TODO
      }
    };

    // TODO: this is wrong but necessary to trick controls
    // @ts-ignore
    canvas.ownerDocument = canvas;
    canvas.getRootNode = () => canvas;
    root.current = events.createRoot(canvas);
    setCanvas(canvas);
    function handleTouch(gestureEvent, type) {
      gestureEvent.persist();
      canvas.dispatchEvent(Object.assign(gestureEvent.nativeEvent, {
        type,
        offsetX: gestureEvent.nativeEvent.locationX,
        offsetY: gestureEvent.nativeEvent.locationY,
        pointerType: 'touch',
        pointerId: gestureEvent.nativeEvent.identifier
      }));
      return true;
    }
    const responder = reactNative.PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponderCapture: () => true,
      onPanResponderTerminationRequest: () => true,
      onStartShouldSetPanResponderCapture: e => handleTouch(e, 'pointercapture'),
      onPanResponderStart: e => handleTouch(e, 'pointerdown'),
      onPanResponderMove: e => handleTouch(e, 'pointermove'),
      onPanResponderEnd: (e, state) => {
        handleTouch(e, 'pointerup');
        if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'click');
      },
      onPanResponderRelease: e => handleTouch(e, 'pointerleave'),
      onPanResponderTerminate: e => handleTouch(e, 'lostpointercapture'),
      onPanResponderReject: e => handleTouch(e, 'lostpointercapture')
    });
    setBind(responder.panHandlers);
  }, []);
  if (root.current && width > 0 && height > 0) {
    root.current.configure({
      gl,
      events: events$1,
      shadows,
      linear,
      flat,
      legacy,
      orthographic,
      frameloop,
      performance,
      raycaster,
      camera,
      scene,
      // expo-gl can only render at native dpr/resolution
      // https://github.com/expo/expo-three/issues/39
      dpr: reactNative.PixelRatio.get(),
      size: {
        width,
        height,
        top,
        left
      },
      // Pass mutable reference to onPointerMissed so it's free to update
      onPointerMissed: (...args) => handlePointerMissed.current == null ? void 0 : handlePointerMissed.current(...args),
      // Overwrite onCreated to apply RN bindings
      onCreated: state => {
        // Bind render to RN bridge
        const context = state.gl.getContext();
        const renderFrame = state.gl.render.bind(state.gl);
        state.gl.render = (scene, camera) => {
          renderFrame(scene, camera);
          context.endFrameEXP();
        };
        return onCreated == null ? void 0 : onCreated(state);
      }
    });
    root.current.render( /*#__PURE__*/jsxRuntime.jsx(Bridge, {
      children: /*#__PURE__*/jsxRuntime.jsx(events.ErrorBoundary, {
        set: setError,
        children: /*#__PURE__*/jsxRuntime.jsx(React__namespace.Suspense, {
          fallback: /*#__PURE__*/jsxRuntime.jsx(events.Block, {
            set: setBlock
          }),
          children: children != null ? children : null
        })
      })
    }));
  }
  React__namespace.useEffect(() => {
    if (canvas) {
      return () => events.unmountComponentAtNode(canvas);
    }
  }, [canvas]);
  return /*#__PURE__*/jsxRuntime.jsx(reactNative.View, {
    ...props,
    ref: viewRef,
    onLayout: onLayout,
    style: {
      flex: 1,
      ...style
    },
    ...bind,
    children: width > 0 && /*#__PURE__*/jsxRuntime.jsx(expoGl.GLView, {
      msaaSamples: antialias ? 4 : 0,
      onContextCreate: onContextCreate,
      style: reactNative.StyleSheet.absoluteFill
    })
  });
});

/**
 * A native canvas which accepts threejs elements as children.
 * @see https://docs.pmnd.rs/react-three-fiber/api/canvas
 */
const Canvas = /*#__PURE__*/React__namespace.forwardRef(function CanvasWrapper(props, ref) {
  return /*#__PURE__*/jsxRuntime.jsx(itsFine.FiberProvider, {
    children: /*#__PURE__*/jsxRuntime.jsx(CanvasImpl, {
      ...props,
      ref: ref
    })
  });
});

/** Default R3F event manager for react-native */
function createTouchEvents(store) {
  const {
    handlePointer
  } = events.createEvents(store);
  const handleTouch = (event, name) => {
    event.persist()

    // Apply offset
    ;
    event.nativeEvent.offsetX = event.nativeEvent.locationX;
    event.nativeEvent.offsetY = event.nativeEvent.locationY;

    // Emulate DOM event
    const callback = handlePointer(name);
    callback(event.nativeEvent);
    return true;
  };
  const responder = reactNative.PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onMoveShouldSetPanResponder: () => true,
    onMoveShouldSetPanResponderCapture: () => true,
    onPanResponderTerminationRequest: () => true,
    onStartShouldSetPanResponderCapture: e => handleTouch(e, 'onPointerCapture'),
    onPanResponderStart: e => handleTouch(e, 'onPointerDown'),
    onPanResponderMove: e => handleTouch(e, 'onPointerMove'),
    onPanResponderEnd: (e, state) => {
      handleTouch(e, 'onPointerUp');
      if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'onClick');
    },
    onPanResponderRelease: e => handleTouch(e, 'onPointerLeave'),
    onPanResponderTerminate: e => handleTouch(e, 'onLostPointerCapture'),
    onPanResponderReject: e => handleTouch(e, 'onLostPointerCapture')
  });
  return {
    priority: 1,
    enabled: true,
    compute(event, state, previous) {
      // https://github.com/pmndrs/react-three-fiber/pull/782
      // Events trigger outside of canvas when moved, use offsetX/Y by default and allow overrides
      state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
      state.raycaster.setFromCamera(state.pointer, state.camera);
    },
    connected: undefined,
    handlers: responder.panHandlers,
    update: () => {
      var _internal$lastEvent;
      const {
        events,
        internal
      } = store.getState();
      if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) {
        handlePointer('onPointerMove')(internal.lastEvent.current);
      }
    },
    connect: () => {
      const {
        set,
        events
      } = store.getState();
      events.disconnect == null ? void 0 : events.disconnect();
      set(state => ({
        events: {
          ...state.events,
          connected: true
        }
      }));
    },
    disconnect: () => {
      const {
        set
      } = store.getState();
      set(state => ({
        events: {
          ...state.events,
          connected: false
        }
      }));
    }
  };
}

// http://stackoverflow.com/questions/105034
function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0,
      v = c == 'x' ? r : r & 0x3 | 0x8;
    return v.toString(16);
  });
}
async function getAsset(input) {
  if (typeof input === 'string') {
    var _NativeModules$BlobMo;
    // Don't process storage
    if (input.startsWith('file:')) return input;

    // Unpack Blobs from react-native BlobManager
    // https://github.com/facebook/react-native/issues/22681#issuecomment-523258955
    if (input.startsWith('blob:') || input.startsWith((_NativeModules$BlobMo = reactNative.NativeModules.BlobModule) == null ? void 0 : _NativeModules$BlobMo.BLOB_URI_SCHEME)) {
      const blob = await new Promise((res, rej) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', input);
        xhr.responseType = 'blob';
        xhr.onload = () => res(xhr.response);
        xhr.onerror = rej;
        xhr.send();
      });
      const data = await new Promise((res, rej) => {
        const reader = new FileReader();
        reader.onload = () => res(reader.result);
        reader.onerror = rej;
        reader.readAsText(blob);
      });
      input = `data:${blob.type};base64,${data}`;
    }

    // Create safe URI for JSI serialization
    if (input.startsWith('data:')) {
      const [header, data] = input.split(';base64,');
      const [, type] = header.split('/');
      const uri = fs__namespace.cacheDirectory + uuidv4() + `.${type}`;
      await fs__namespace.writeAsStringAsync(uri, data, {
        encoding: fs__namespace.EncodingType.Base64
      });
      return uri;
    }
  }

  // Download bundler module or external URL
  const asset = await expoAsset.Asset.fromModule(input).downloadAsync();
  let uri = asset.localUri || asset.uri;

  // Unpack assets in Android Release Mode
  if (!uri.includes(':')) {
    const file = `${fs__namespace.cacheDirectory}ExponentAsset-${asset.hash}.${asset.type}`;
    await fs__namespace.copyAsync({
      from: uri,
      to: file
    });
    uri = file;
  }
  return uri;
}
function polyfills() {
  // Patch Blob for ArrayBuffer and URL if unsupported
  // https://github.com/facebook/react-native/pull/39276
  // https://github.com/pmndrs/react-three-fiber/issues/3058
  if (reactNative.Platform.OS !== 'web') {
    try {
      const blob = new Blob([new ArrayBuffer(4)]);
      const url = URL.createObjectURL(blob);
      URL.revokeObjectURL(url);
    } catch (_) {
      const BlobManager = require('react-native/Libraries/Blob/BlobManager.js');
      const createObjectURL = URL.createObjectURL;
      URL.createObjectURL = function (blob) {
        if (blob.data._base64) {
          return `data:${blob.type};base64,${blob.data._base64}`;
        }
        return createObjectURL(blob);
      };
      const createFromParts = BlobManager.createFromParts;
      BlobManager.createFromParts = function (parts, options) {
        parts = parts.map(part => {
          if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
            part = base64Js.fromByteArray(new Uint8Array(part));
          }
          return part;
        });
        const blob = createFromParts(parts, options);

        // Always enable slow but safe path for iOS (previously for Android unauth)
        // https://github.com/pmndrs/react-three-fiber/issues/3075
        // if (!NativeModules.BlobModule?.BLOB_URI_SCHEME) {
        blob.data._base64 = '';
        for (const part of parts) {
          var _data$_base, _data;
          blob.data._base64 += (_data$_base = (_data = part.data) == null ? void 0 : _data._base64) != null ? _data$_base : part;
        }
        // }

        return blob;
      };
    }
  }

  // Don't pre-process urls, let expo-asset generate an absolute URL
  const extractUrlBase = THREE__namespace.LoaderUtils.extractUrlBase.bind(THREE__namespace.LoaderUtils);
  THREE__namespace.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';

  // There's no Image in native, so create a data texture instead
  THREE__namespace.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
    if (this.path && typeof url === 'string') url = this.path + url;
    const texture = new THREE__namespace.Texture();
    getAsset(url).then(async uri => {
      // https://github.com/expo/expo-three/pull/266
      const {
        width,
        height
      } = await new Promise((res, rej) => reactNative.Image.getSize(uri, (width, height) => res({
        width,
        height
      }), rej));
      texture.image = {
        // Special case for EXGLImageUtils::loadImage
        data: {
          localUri: uri
        },
        width,
        height
      };
      texture.flipY = true; // Since expo-gl@12.4.0
      texture.needsUpdate = true;

      // Force non-DOM upload for EXGL texImage2D
      // @ts-expect-error
      texture.isDataTexture = true;
      onLoad == null ? void 0 : onLoad(texture);
    }).catch(onError);
    return texture;
  };

  // Fetches assets via FS
  THREE__namespace.FileLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
    if (this.path && typeof url === 'string') url = this.path + url;
    this.manager.itemStart(url);
    getAsset(url).then(async uri => {
      const base64 = await fs__namespace.readAsStringAsync(uri, {
        encoding: fs__namespace.EncodingType.Base64
      });
      const data = buffer.Buffer.from(base64, 'base64');
      onLoad == null ? void 0 : onLoad(data.buffer);
    }).catch(error => {
      onError == null ? void 0 : onError(error);
      this.manager.itemError(url);
    }).finally(() => {
      this.manager.itemEnd(url);
    });
  };
}

if (reactNative.Platform.OS !== 'web') polyfills();

exports.ReactThreeFiber = events.threeTypes;
exports._roots = events.roots;
exports.act = events.act;
exports.addAfterEffect = events.addAfterEffect;
exports.addEffect = events.addEffect;
exports.addTail = events.addTail;
exports.advance = events.advance;
exports.applyProps = events.applyProps;
exports.buildGraph = events.buildGraph;
exports.context = events.context;
exports.createEvents = events.createEvents;
exports.createPointerEvents = events.createPointerEvents;
exports.createPortal = events.createPortal;
exports.createRoot = events.createRoot;
exports.dispose = events.dispose;
exports.extend = events.extend;
exports.flushGlobalEffects = events.flushGlobalEffects;
exports.flushSync = events.flushSync;
exports.getRootState = events.getRootState;
exports.invalidate = events.invalidate;
exports.reconciler = events.reconciler;
exports.render = events.render;
exports.unmountComponentAtNode = events.unmountComponentAtNode;
exports.useFrame = events.useFrame;
exports.useGraph = events.useGraph;
exports.useInstanceHandle = events.useInstanceHandle;
exports.useLoader = events.useLoader;
exports.useStore = events.useStore;
exports.useThree = events.useThree;
exports.Canvas = Canvas;
exports.events = createTouchEvents;
