import { join } from 'path'
import fs from 'fs'
import { useEffect } from 'react'
import { subscribe, useSnapshot } from 'valtio'
import { usedServerPathsV1 } from 'flying-squid/dist/lib/modules/world'
import { openURL } from 'renderer/viewer/lib/simpleUtils'
import { Vec3 } from 'vec3'
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
import { subscribeKey } from 'valtio/utils'
import { ErrorBoundary } from '@zardoy/react-util'
import {
  activeModalStack,
  showModal,
  hideModal,
  miscUiState,
  openOptionsMenu,
  gameAdditionalState
} from '../globalState'
import { fsState } from '../loadSave'
import { disconnect } from '../flyingSquidUtils'
import { openGithub, pointerLock } from '../utils'
import { setLoadingScreenStatus } from '../appStatus'
import { closeWan, openToWanAndCopyJoinLink, getJoinLink } from '../localServerMultiplayer'
import { collectFilesToCopy, fileExistsAsyncOptimized, mkdirRecursive, uniqueFileNameFromWorldName } from '../browserfs'
import { appQueryParams } from '../appParams'
import { downloadPacketsReplay, packetsRecordingState } from '../packetsReplay/packetsReplayLegacy'
import { options } from '../optionsStorage'
import { useIsModalActive } from './utilsApp'
import { showOptionsModal } from './SelectOption'
import Button from './Button'
import Screen from './Screen'
import styles from './PauseScreen.module.css'
import { DiscordButton } from './DiscordButton'
import { showNotification } from './NotificationProvider'
import { appStatusState, lastConnectOptions, reconnectReload } from './AppStatusProvider'
import NetworkStatus from './NetworkStatus'
import PauseLinkButtons from './PauseLinkButtons'
import { pixelartIcons } from './PixelartIcon'
import LoadingTimer from './LoadingTimer'

const waitForPotentialRender = async () => {
  return new Promise<void>(resolve => {
    requestAnimationFrame(() => requestAnimationFrame(resolve as any))
  })
}

export const saveToBrowserMemory = async () => {
  setLoadingScreenStatus('Saving world')
  try {
    await new Promise<void>(resolve => {
      subscribeKey(appStatusState, 'isDisplaying', () => {
        if (appStatusState.isDisplaying) {
          resolve()
        }
      })
    })
    const worldFolder = fsState.inMemorySavePath
    const saveRootPath = await uniqueFileNameFromWorldName(worldFolder.split('/').pop()!, `/data/worlds`)
    await mkdirRecursive(saveRootPath)
    console.log('made world folder', saveRootPath)
    const allRootPaths = [...usedServerPathsV1]
    const allFilesToCopy = [] as string[]
    for (const dirBase of allRootPaths) {
      // eslint-disable-next-line no-await-in-loop
      if (dirBase.includes('.') && await fileExistsAsyncOptimized(join(worldFolder, dirBase))) {
        allFilesToCopy.push(dirBase)
        continue
      }
      // eslint-disable-next-line no-await-in-loop
      let res = await collectFilesToCopy(join(worldFolder, dirBase), true)
      if (dirBase === 'region') {
        res = res.filter(x => x.endsWith('.mca'))
      }
      allFilesToCopy.push(...res.map(x => join(dirBase, x)))
    }
    console.log('paths collected')
    const pathsSplitBasic = allFilesToCopy.filter(path => {
      if (!path.startsWith('region/')) return true
      const [x, z] = path.split('/').at(-1)!.split('.').slice(1, 3).map(Number)
      return Math.abs(x) > 50 || Math.abs(z) > 50 // HACK: otherwise it's too big and we can't handle it in visual display
    })
    let copied = 0
    let isRegionFiles = false
    const upProgress = (totalSize: number) => {
      copied++
      let action = fsState.remoteBackend ? 'Downloading & copying' : 'Copying'
      action += isRegionFiles ? ' region files (world chunks)' : ' basic save files'
      setLoadingScreenStatus(`${action} files (${copied}/${totalSize})`)
    }
    const copyFiles = async (copyPaths: string[][]) => {
      const totalSIze = copyPaths.flat().length
      for (const copyFileGroup of copyPaths) {
        // eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-loop-func
        await Promise.all(copyFileGroup.map(async (copyPath) => {
          const srcPath = join(worldFolder, copyPath)
          const savePath = join(saveRootPath, copyPath)
          await mkdirRecursive(savePath)
          await fs.promises.writeFile(savePath, await fs.promises.readFile(srcPath) as any)
          upProgress(totalSIze)
          if (isRegionFiles) {
            const regionFile = copyPath.split('/').at(-1)!
            appStatusState.loadingChunksData![regionFile] = 'done'
          }
        }))
        // eslint-disable-next-line no-await-in-loop
        await waitForPotentialRender()
      }
    }
    // basic save files
    await copyFiles(splitByCopySize(pathsSplitBasic))
    setLoadingScreenStatus('Preparing world chunks copying')
    await waitForPotentialRender()

    // region files
    isRegionFiles = true
    copied = 0
    const regionFiles = allFilesToCopy.filter(x => !pathsSplitBasic.includes(x))
    const regionFilesNumbers = regionFiles.map(x => x.split('/').at(-1)!.split('.').slice(1, 3).map(Number))
    const xMin = Math.min(...regionFilesNumbers.flatMap(x => x[0]))
    const zMin = Math.min(...regionFilesNumbers.flatMap(x => x[1]))
    const xMax = Math.max(...regionFilesNumbers.flatMap(x => x[0]))
    const zMax = Math.max(...regionFilesNumbers.flatMap(x => x[1]))
    const playerPosRegion = bot.entity.position.divide(new Vec3(32 * 16, 32 * 16, 32 * 16)).floored()
    const maxDistantRegion = Math.max(
      Math.abs(playerPosRegion.x - xMin),
      Math.abs(playerPosRegion.z - zMin),
      Math.abs(playerPosRegion.x - xMax),
      Math.abs(playerPosRegion.z - zMax)
    )
    const spiral = generateSpiralMatrix(maxDistantRegion)
    const filesWithSpiral = spiral.filter(x => allFilesToCopy.includes(`region/r.${x[0]}.${x[1]}.mca`)).map(x => `region/r.${x[0]}.${x[1]}.mca`)
    if (filesWithSpiral.length !== regionFiles.length) throw new Error('Something went wrong with region files')

    appStatusState.loadingChunksData = Object.fromEntries(regionFiles.map(x => [x.split('/').at(-1)!, 'loading']))
    appStatusState.loadingChunksDataPlayerChunk = { x: playerPosRegion.x, z: playerPosRegion.z }
    await copyFiles(splitByCopySize(filesWithSpiral, 10))

    return saveRootPath
  } catch (err) {
    console.error(err)
    void showOptionsModal(`Error while saving the world: ${err.message}`, [])
  } finally {
    setLoadingScreenStatus(undefined)
  }
}

const splitByCopySize = (files: string[], copySize = 15) => {
  return files.reduce<string[][]>((acc, cur, i) => {
    if (i % copySize === 0) {
      acc.push([])
    }
    acc.at(-1)!.push(cur)
    return acc
  }, [])
}

export default () => {
  const lockConnect = appQueryParams.lockConnect === 'true'
  const isModalActive = useIsModalActive('pause-screen')
  const fsStateSnap = useSnapshot(fsState)
  const activeModalStackSnap = useSnapshot(activeModalStack)
  const { singleplayer, wanOpened, wanOpening } = useSnapshot(miscUiState)
  const { noConnection } = useSnapshot(gameAdditionalState)
  const { active: packetsReplaceActive, hasRecordedPackets: packetsReplaceHasRecordedPackets } = useSnapshot(packetsRecordingState)
  const { displayRecordButton: displayPacketsButtons } = useSnapshot(options)
  const { appConfig } = useSnapshot(miscUiState)

  const handlePointerLockChange = () => {
    if (!pointerLock.hasPointerLock && activeModalStack.length === 0) {
      showModal({ reactType: 'pause-screen' })
    }
  }

  useEffect(() => {
    document.addEventListener('pointerlockchange', handlePointerLockChange)

    return () => {
      document.removeEventListener('pointerlockchange', handlePointerLockChange)
    }
  }, [])

  const onReturnPress = () => {
    hideModal({ reactType: 'pause-screen' })
  }

  const clickWebShareButton = async () => {
    if (!wanOpened) return
    try {
      const url = getJoinLink()
      const shareData = { url }
      await navigator.share?.(shareData)
    } catch (err) {
      console.log(`Error: ${err}`)
    }
  }

  const clickJoinLinkButton = async (qr = false) => {
    if (!qr && wanOpened) {
      closeWan()
      return
    }
    if (!wanOpened || !qr) {
      await openToWanAndCopyJoinLink((err) => {
        if (!miscUiState.wanOpening) return
        alert(`Something went wrong: ${err}`)
      }, !qr)
    }
    if (qr) {
      const joinLink = getJoinLink()
      miscUiState.currentDisplayQr = joinLink ?? null
    }
  }

  const openWorldActions = async () => {
    if (fsStateSnap.inMemorySave || !singleplayer) {
      return showOptionsModal('World actions...', [])
    }
    const action = await showOptionsModal('World actions...', ['Save to browser memory'])
    if (action === 'Save to browser memory') {
      const path = await saveToBrowserMemory()
      if (!path) return
      const saveName = path.split('/').at(-1)
      showNotification(`World saved to ${saveName}`, 'Load it to keep your progress!')
      // fsState.inMemorySave = true
      // fsState.syncFs = false
      // fsState.isReadonly = false
      // fsState.remoteBackend = false
    }
  }

  if (!isModalActive) return null

  return <Screen title='Game Menu'>
    <div style={{ position: 'fixed', top: '5px', left: 'calc(env(safe-area-inset-left) + 5px)', display: 'flex', flexDirection: 'column', gap: '5px' }}>
      <Button
        icon="pixelarticons:folder"
        onClick={async () => openWorldActions()}
      />
      {displayPacketsButtons && (
        <>
          <Button
            icon={packetsReplaceActive ? 'pixelarticons:debug-stop' : 'pixelarticons:circle'}
            onClick={() => {
              packetsRecordingState.active = !packetsRecordingState.active
            }}
          />
          {packetsReplaceHasRecordedPackets && (
            <Button
              icon={pixelartIcons['briefcase-download']}
              onClick={async () => downloadPacketsReplay()}
            />
          )}
          <Button
            icon={pixelartIcons['download']}
            onClick={async () => bot.downloadCurrentWorldState()}
          />
        </>
      )}
    </div>
    <ErrorBoundary renderError={() => <div>error</div>}>
      <div style={{ position: 'fixed', top: '5px', left: 'calc(env(safe-area-inset-left) + 35px)' }}>
        <NetworkStatus />
      </div>
    </ErrorBoundary>
    <div className={styles.pause_container}>
      <Button className="button" style={{ width: '204px' }} onClick={onReturnPress}>Back to Game</Button>
      <PauseLinkButtons />
      <Button className="button" style={{ width: '204px' }} onClick={() => openOptionsMenu('main')}>Options...</Button>
      {singleplayer ? (
        <div className={styles.row}>
          <Button className="button" style={{ width: '170px' }} onClick={async () => clickJoinLinkButton()}>
            {wanOpening ? 'Opening, wait...' : wanOpened ? 'Close Wan' : 'Copy Join Link'}
          </Button>
          {(navigator.share as typeof navigator.share | undefined) ? (
            <Button
              title="Share Join Link"
              className="button"
              icon="pixelarticons:arrow-up"
              style={{ width: '20px' }}
              onClick={async () => clickWebShareButton()}
            />
          ) : null}
          <Button
            title='Display QR for the Join Link'
            className="button"
            icon="pixelarticons:dice"
            style={{ width: '20px' }}
            onClick={async () => clickJoinLinkButton(true)}
          />
        </div>
      ) : null}
      {!lockConnect && <>
        <Button className="button" style={{ width: '204px' }} onClick={disconnect}>
          {fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
        </Button>
      </>}
      {(noConnection || appConfig?.alwaysReconnectButton) && (
        <div className={styles.row}>
          <Button className="button" style={{ width: appConfig?.reportBugButtonWithReconnect ? '98px' : '204px' }} onClick={reconnectReload}>
            Reconnect
          </Button>
          {appConfig?.reportBugButtonWithReconnect && (
            <Button
              label="Report Problem"
              className="button"
              style={{ width: '98px' }}
              onClick={async () => {
                const platform = (navigator as any).userAgentData?.platform ?? navigator.platform
                const body = `Version: ${window.location.hostname}\nServer: ${lastConnectOptions.value?.server ?? '<not a server>'}\nPlatform: ${platform}\nWebsite: ${window.location.href}`
                const currentHost = window.location.hostname
                const options = [
                  'GitHub (please use it if you can)',
                  'Email',
                  ...((currentHost === 'mcraft.fun' || currentHost === 'ru.mcraft.fun') ? ['Try Beta Version'] : []),
                  // 'Use previous versions of client'
                ]
                const action = await showOptionsModal('Report client issue', options)
                if (!action) return

                switch (action) {
                  case 'GitHub (please use it if you can)':
                    openGithub(`/issues/new?body=${encodeURIComponent(body)}&title=${encodeURIComponent('[Bug Report] <describe your issue here>')}&labels=bug`)
                    break
                  case 'Email': {
                    window.location.href = `mailto:support@mcraft.fun?body=${encodeURIComponent(body)}`
                    break
                  }
                  case 'Try Beta Version': {
                    if (currentHost === 'mcraft.fun') {
                      window.location.href = 'https://s.mcraft.fun'
                    } else if (currentHost === 'ru.mcraft.fun') {
                      window.location.href = 'https://s.pcm.gg'
                    }
                    break
                  }
                  case 'Use previous versions of client':
                    // TODO: Implement versions screen
                    void showOptionsModal('Previous versions', [])
                    break
                }
              }}
            />
          )}
        </div>
      )}
    </div>
    <LoadingTimer />
  </Screen>
}
