/**
 * @remix-run/react v1.14.3
 *
 * Copyright (c) Remix Software Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.md file in the root directory of this source tree.
 *
 * @license MIT
 */
import * as React from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { RemixContext } from './components.js';
import { RemixErrorBoundary, RemixRootDefaultErrorBoundary } from './errorBoundaries.js';
import { deserializeErrors } from './errors.js';
import { createClientRoutes } from './routes.js';

/* eslint-disable prefer-let/prefer-let */

let router;
let hmrAbortController;
if (import.meta && import.meta.hot) {
  import.meta.hot.accept("remix:manifest", async newManifest => {
    let routeIds = [...new Set(router.state.matches.map(m => m.route.id).concat(Object.keys(window.__remixRouteModules)))];

    // Load new route modules that we've seen.
    let newRouteModules = Object.assign({}, window.__remixRouteModules, Object.fromEntries((await Promise.all(routeIds.map(async id => {
      var _newManifest$hmr, _window$__remixRouteM, _window$__remixRouteM2, _window$__remixRouteM3;
      if (!newManifest.routes[id]) {
        return null;
      }
      let imported = await import(newManifest.routes[id].module + `?t=${(_newManifest$hmr = newManifest.hmr) === null || _newManifest$hmr === void 0 ? void 0 : _newManifest$hmr.timestamp}`);
      return [id, {
        ...imported,
        // react-refresh takes care of updating these in-place,
        // if we don't preserve existing values we'll loose state.
        default: imported.default ? ((_window$__remixRouteM = window.__remixRouteModules[id]) === null || _window$__remixRouteM === void 0 ? void 0 : _window$__remixRouteM.default) ?? imported.default : imported.default,
        CatchBoundary: imported.CatchBoundary ? ((_window$__remixRouteM2 = window.__remixRouteModules[id]) === null || _window$__remixRouteM2 === void 0 ? void 0 : _window$__remixRouteM2.CatchBoundary) ?? imported.CatchBoundary : imported.CatchBoundary,
        ErrorBoundary: imported.ErrorBoundary ? ((_window$__remixRouteM3 = window.__remixRouteModules[id]) === null || _window$__remixRouteM3 === void 0 ? void 0 : _window$__remixRouteM3.ErrorBoundary) ?? imported.ErrorBoundary : imported.ErrorBoundary
      }];
    }))).filter(Boolean)));
    Object.assign(window.__remixRouteModules, newRouteModules);
    // Create new routes
    let routes = createClientRoutes(newManifest.routes, window.__remixRouteModules, window.__remixContext.future);

    // This is temporary API and will be more granular before release
    router._internalSetRoutes(routes);
    if (hmrAbortController) {
      hmrAbortController.abort();
    }
    hmrAbortController = new AbortController();
    let signal = hmrAbortController.signal;
    // Wait for router to be idle before updating the manifest and route modules
    // and triggering a react-refresh
    let unsub = router.subscribe(state => {
      if (state.revalidation === "idle" && !signal.aborted) {
        unsub();
        // TODO: Handle race conditions here. Should abort if a new update
        // comes in while we're waiting for the router to be idle.
        Object.assign(window.__remixManifest, newManifest);
        window.$RefreshRuntime$.performReactRefresh();
      }
    });
    router.revalidate();
  });
}

/**
 * The entry point for a Remix app when it is rendered in the browser (in
 * `app/entry.client.js`). This component is used by React to hydrate the HTML
 * that was received from the server.
 */
function RemixBrowser(_props) {
  if (!router) {
    let routes = createClientRoutes(window.__remixManifest.routes, window.__remixRouteModules, window.__remixContext.future);
    let hydrationData = window.__remixContext.state;
    if (hydrationData && hydrationData.errors) {
      hydrationData = {
        ...hydrationData,
        errors: deserializeErrors(hydrationData.errors)
      };
    }
    router = createBrowserRouter(routes, {
      hydrationData
    });
  }

  // We need to include a wrapper RemixErrorBoundary here in case the root error
  // boundary also throws and we need to bubble up outside of the router entirely.
  // Then we need a stateful location here so the user can back-button navigate
  // out of there
  let location = useSyncExternalStore(router.subscribe, () => router.state.location, () => router.state.location);
  return /*#__PURE__*/React.createElement(RemixContext.Provider, {
    value: {
      manifest: window.__remixManifest,
      routeModules: window.__remixRouteModules,
      future: window.__remixContext.future
    }
  }, /*#__PURE__*/React.createElement(RemixErrorBoundary, {
    location: location,
    component: RemixRootDefaultErrorBoundary
  }, /*#__PURE__*/React.createElement(RouterProvider, {
    router: router,
    fallbackElement: null
  })));
}

export { RemixBrowser };
