import { hideModal, isGameActive, miscUiState, showModal } from './globalState'
import { options } from './optionsStorage'
import { appStatusState } from './react/AppStatusProvider'
import { notificationProxy, showNotification } from './react/NotificationProvider'

export const goFullscreen = async (doToggle = false) => {
  if (!document.fullscreenElement) {
    // todo display a message or repeat?
    await document.documentElement.requestFullscreen().catch(() => { })
    // request full keyboard access
    //@ts-expect-error
    navigator.keyboard?.lock?.(['Escape', 'KeyW'])
  } else if (doToggle) {
    await document.exitFullscreen().catch(() => { })
  }
}

export const toNumber = (val) => {
  const num = Number(val)
  return isNaN(num) ? undefined : num
}

export const inGameError = (err) => {
  console.error(err)
  // todo report
  miscUiState.hasErrors = true
}

export const pointerLock = {
  get hasPointerLock () {
    return document.pointerLockElement
  },
  justHitEscape: false,
  async requestPointerLock () {
    if (!isGameActive(true) || !document.documentElement.requestPointerLock || miscUiState.currentTouch) {
      return
    }
    if (options.autoFullScreen) {
      void goFullscreen()
    }
    const displayBrowserProblem = () => {
      showNotification('Browser Delay Limitation', navigator['keyboard'] ? 'Click on screen, enable Auto Fullscreen or F11' : 'Click on screen or use fullscreen in Chrome')
      notificationProxy.id = 'pointerlockchange'
    }
    if (!(document.fullscreenElement && navigator['keyboard']) && this.justHitEscape) {
      displayBrowserProblem()
    } else {
      //@ts-expect-error
      const promise: any = document.documentElement.requestPointerLock({
        unadjustedMovement: options.mouseRawInput
      })
      promise?.catch((error) => {
        if (error.name === 'NotSupportedError') {
          // Some platforms may not support unadjusted movement, request again a regular pointer lock.
          document.documentElement.requestPointerLock()
        } else if (error.name === 'SecurityError') {
          // cause: https://discourse.threejs.org/t/how-to-avoid-pointerlockcontrols-error/33017/4
          displayBrowserProblem()
        } else {
          console.error(error)
        }
      })
    }
    this.justHitEscape = false
  }
}

window.getScreenRefreshRate = getScreenRefreshRate

/**
 * Allows to obtain the estimated Hz of the primary monitor in the system.
 */
export async function getScreenRefreshRate (): Promise<number> {
  let requestId = null as number | null
  let callbackTriggered = false
  let resolve

  const DOMHighResTimeStampCollection = [] as number[]

  const triggerAnimation = (DOMHighResTimeStamp) => {
    DOMHighResTimeStampCollection.unshift(DOMHighResTimeStamp)

    if (DOMHighResTimeStampCollection.length > 10) {
      const t0 = DOMHighResTimeStampCollection.pop()!
      const fps = Math.floor(1000 * 10 / (DOMHighResTimeStamp - t0))

      if (!callbackTriggered || fps > 1000) {
        resolve(Math.min(fps, 1000)/* , DOMHighResTimeStampCollection */)
      }

      callbackTriggered = true
    }

    requestId = window.requestAnimationFrame(triggerAnimation)
  }

  window.requestAnimationFrame(triggerAnimation)

  window.setTimeout(() => {
    window.cancelAnimationFrame(requestId!)
    requestId = null
  }, 500)

  return new Promise(_resolve => {
    resolve = _resolve
  })
}

export const getGamemodeNumber = (bot) => {
  switch (bot.game.gameMode) {
    case 'survival': return 0
    case 'creative': return 1
    case 'adventure': return 2
    case 'spectator': return 3
    default: return -1
  }
}

export const isMajorVersionGreater = (ver1: string, ver2: string) => {
  const [a1, b1] = ver1.split('.')
  const [a2, b2] = ver2.split('.')
  return +a1 > +a2 || (+a1 === +a2 && +b1 > +b2)
}

let ourLastStatus: string | undefined = ''
export const setLoadingScreenStatus = function (status: string | undefined | null, isError = false, hideDots = false, fromFlyingSquid = false) {
  // null can come from flying squid, should restore our last status
  if (status === null) {
    status = ourLastStatus
  } else if (!fromFlyingSquid) {
    ourLastStatus = status
  }
  fromFlyingSquid = false

  if (status === undefined) {
    appStatusState.status = ''

    hideModal({ reactType: 'app-status' }, {}, { force: true })
    return
  }

  // todo update in component instead
  showModal({ reactType: 'app-status' })
  if (appStatusState.isError) {
    miscUiState.gameLoaded = false
    return
  }
  appStatusState.hideDots = hideDots
  appStatusState.isError = isError
  appStatusState.lastStatus = isError ? appStatusState.status : ''
  appStatusState.status = status
}

// doesn't support snapshots
export const toMajorVersion = (version) => {
  const [a, b] = (String(version)).split('.')
  return `${a}.${b}`
}

let prevRenderDistance = options.renderDistance
export const setRenderDistance = () => {
  assertDefined(worldView)
  const { renderDistance: singleplayerRenderDistance, multiplayerRenderDistance } = options
  let renderDistance = miscUiState.singleplayer ? singleplayerRenderDistance : multiplayerRenderDistance
  const zeroRenderDistance = miscUiState.singleplayer && renderDistance === 0
  if (zeroRenderDistance) {
    renderDistance = 1 // mineflayer limitation workaround
  }
  bot.setSettings({
    viewDistance: renderDistance
  })
  if (zeroRenderDistance) {
    localServer!.players[0].view = 0
    renderDistance = 0
  }
  worldView.updateViewDistance(renderDistance)
  prevRenderDistance = renderDistance
}
export const reloadChunks = async () => {
  if (!worldView) return
  setRenderDistance()
  await worldView.updatePosition(bot.entity.position, true)
}

export const openGithub = () => {
  window.open(process.env.GITHUB_URL, '_blank')
}

export const resolveTimeout = async (promise, timeout = 10_000) => {
  return new Promise((resolve, reject) => {
    promise.then(resolve, reject)
    setTimeout(() => {
      reject(new Error('timeout'))
    }, timeout)
  })
}

export function assertDefined<T> (x: T | undefined): asserts x is T {
  if (!x) throw new Error('Assertion failed. Something is not available')
}

export const haveDirectoryPicker = () => {
  return !!window.showDirectoryPicker
}

const reportedWarnings = new Set<string>()

export const reportWarningOnce = (id: string, message: string) => {
  if (reportedWarnings.has(id)) return
  reportedWarnings.add(id)
  console.warn(message)
}
