/* global HAS_REACT_18 */
import React from "react"
import ReactDOM from "react-dom"

import socketIo from "./socketIo"
import emitter from "./emitter"
import { apiRunner, apiRunnerAsync } from "./api-runner-browser"
import { setLoader, publicLoader } from "./loader"
import { Indicator } from "./loading-indicator/indicator"
import DevLoader from "./dev-loader"
import asyncRequires from "$virtual/async-requires"
// Generated during bootstrap
import matchPaths from "$virtual/match-paths.json"
import { LoadingIndicatorEventHandler } from "./loading-indicator"
import Root from "./root"
import { init as navigationInit } from "./navigation"
// ensure in develop we have at least some .css (even if it's empty).
// this is so there is no warning about not matching content-type when site doesn't include any regular css (for example when css-in-js is used)
// this also make sure that if all css is removed in develop we are not left with stale commons.css that have stale content
import "./blank.css"

// Enable fast-refresh for virtual sync-requires, gatsby-browser & navigation
// To ensure that our <Root /> component can hot reload in case anything below doesn't
// satisfy fast-refresh constraints
module.hot.accept(
  [`$virtual/async-requires`, `./api-runner-browser`, `./navigation`],
  () => {
    // asyncRequires should be automatically updated here (due to ESM import and webpack HMR spec),
    // but loader doesn't know that and needs to be manually nudged
    loader.updateAsyncRequires(asyncRequires)
  }
)

window.___emitter = emitter

const loader = new DevLoader(asyncRequires, matchPaths)
setLoader(loader)
loader.setApiRunner(apiRunner)

window.___loader = publicLoader

let reactFirstRenderOrHydrate
if (HAS_REACT_18) {
  const reactDomClient = require(`react-dom/client`)
  reactFirstRenderOrHydrate = (Component, el) => {
    // we will use hydrate if mount element has any content inside
    const useHydrate = el && el.children.length

    if (useHydrate) {
      const root = reactDomClient.hydrateRoot(el, Component)
      return () => root.unmount()
    } else {
      const root = reactDomClient.createRoot(el)
      root.render(Component)
      return () => root.unmount()
    }
  }
} else {
  const reactDomClient = require(`react-dom`)
  reactFirstRenderOrHydrate = (Component, el) => {
    // we will use hydrate if mount element has any content inside
    const useHydrate = el && el.children.length

    if (useHydrate) {
      reactDomClient.hydrate(Component, el)
      return () => ReactDOM.unmountComponentAtNode(el)
    } else {
      reactDomClient.render(Component, el)
      return () => ReactDOM.unmountComponentAtNode(el)
    }
  }
}

// Do dummy dynamic import so the jsonp __webpack_require__.e is added to the commons.js
// bundle. This ensures hot reloading doesn't break when someone first adds
// a dynamic import.
//
// Without this, the runtime breaks with a
// "TypeError: __webpack_require__.e is not a function"
// error.
export function notCalledFunction() {
  return import(`./dummy`)
}

// Let the site/plugins run code very early.
apiRunnerAsync(`onClientEntry`).then(() => {
  // Hook up the client to socket.io on server
  const socket = socketIo()
  if (socket) {
    socket.on(`reload`, () => {
      window.location.reload()
    })
  }

  /**
   * Service Workers are persistent by nature. They stick around,
   * serving a cached version of the site if they aren't removed.
   * This is especially frustrating when you need to test the
   * production build on your local machine.
   *
   * Let's warn if we find service workers in development.
   */
  if (`serviceWorker` in navigator) {
    navigator.serviceWorker.getRegistrations().then(registrations => {
      if (registrations.length > 0)
        console.warn(
          `Warning: found one or more service workers present.`,
          `If your site isn't behaving as expected, you might want to remove these.`,
          registrations
        )
    })
  }

  const rootElement = document.getElementById(`___gatsby`)
  const renderer = apiRunner(
    `replaceHydrateFunction`,
    undefined,
    reactFirstRenderOrHydrate
  )[0]

  let dismissLoadingIndicator
  if (
    process.env.GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND &&
    process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR === `true`
  ) {
    let indicatorMountElement
    let cleanupFn

    const showIndicatorTimeout = setTimeout(() => {
      indicatorMountElement = document.createElement(
        `first-render-loading-indicator`
      )
      document.body.append(indicatorMountElement)
      cleanupFn = renderer(<Indicator />, indicatorMountElement)
    }, 1000)

    dismissLoadingIndicator = () => {
      clearTimeout(showIndicatorTimeout)
      if (indicatorMountElement) {
        // If user defined replaceHydrateFunction themselves the cleanupFn return might not be there
        // So fallback to unmountComponentAtNode for now
        if (cleanupFn && typeof cleanupFn === `function`) {
          cleanupFn()
        } else {
          ReactDOM.unmountComponentAtNode(indicatorMountElement)
        }
        indicatorMountElement.remove()
      }
    }
  }

  Promise.all([
    loader.loadPage(`/dev-404-page/`),
    loader.loadPage(`/404.html`),
    loader.loadPage(window.location.pathname + window.location.search),
  ]).then(() => {
    navigationInit()

    function onHydrated() {
      apiRunner(`onInitialClientRender`)

      // Render query on demand overlay
      if (
        process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR &&
        process.env.GATSBY_QUERY_ON_DEMAND_LOADING_INDICATOR === `true`
      ) {
        const indicatorMountElement = document.createElement(`div`)
        indicatorMountElement.setAttribute(
          `id`,
          `query-on-demand-indicator-element`
        )
        document.body.append(indicatorMountElement)

        renderer(<LoadingIndicatorEventHandler />, indicatorMountElement)
      }
    }

    function App() {
      const onClientEntryRanRef = React.useRef(false)

      React.useEffect(() => {
        if (!onClientEntryRanRef.current) {
          onClientEntryRanRef.current = true

          onHydrated()
        }
      }, [])

      return <Root />
    }

    function runRender() {
      if (dismissLoadingIndicator) {
        dismissLoadingIndicator()
      }

      renderer(<App />, rootElement)
    }

    // https://github.com/madrobby/zepto/blob/b5ed8d607f67724788ec9ff492be297f64d47dfc/src/zepto.js#L439-L450
    // TODO remove IE 10 support
    const doc = document
    if (
      doc.readyState === `complete` ||
      (doc.readyState !== `loading` && !doc.documentElement.doScroll)
    ) {
      setTimeout(function () {
        runRender()
      }, 0)
    } else {
      const handler = function () {
        doc.removeEventListener(`DOMContentLoaded`, handler, false)
        window.removeEventListener(`load`, handler, false)

        runRender()
      }

      doc.addEventListener(`DOMContentLoaded`, handler, false)
      window.addEventListener(`load`, handler, false)
    }
  })
})
