/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

import * as THREE from 'three'
import React, { useRef, useState, useEffect, useMemo } from 'react'
import { useFrame } from 'react-three-fiber'
import { useGLTF } from '@react-three/drei/useGLTF'

import cloneGltf from '../lib/cloneGltf'

import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
import { FrontSide } from 'three/src/constants'

import { HEIGHT } from '../Player'

import api, { Transform } from '../../store'
import { MeshNormalMaterial } from 'three/src/materials/MeshNormalMaterial'
import { Object3D } from 'three/src/core/Object3D'
import { SkinnedMesh } from 'three/src/objects/SkinnedMesh'

type GLTFResult = GLTF & {
  nodes: {
    mesh: THREE.SkinnedMesh
    mixamorigHips: THREE.Bone
  }
  materials: {
    ['Material.001']: THREE.MeshStandardMaterial
  }
}

export type ActionName =
  | 'idle'
  | 'jump'
  | 'run_left'
  | 'run_right'
  | 'run'
  | 'turn_left'
  | 'turn_right'
  | 'walk'
  | 'walk_backwards'
  | 'walk_left'
  | 'walk_right'

type GLTFActions = Record<ActionName, THREE.AnimationAction>

const Human: React.FC<JSX.IntrinsicElements['group'] & { id: number }> = ({
  id,
  ...rest
}) => {
  const group = useRef<THREE.Group>()
  const gltf = useGLTF('/model/human.glb') as GLTFResult
  const currentAnimation = useRef<ActionName>('idle')

  const { scene, animations } = cloneGltf(gltf as GLTFResult)

  const [nodes] = useState<Record<string, SkinnedMesh[]>>(() => {
    const n = {}
    scene.children[0].children.forEach((child: Object3D) => {
      //@ts-ignore
      n[child.name] = child
    })
    return n
  })

  const material = useMemo(() => {
    const m = new MeshNormalMaterial({
      transparent: true,
      side: FrontSide,
      flatShading: false,
      skinning: true,
    })

    m.onBeforeCompile = function (shader) {
      shader.fragmentShader = shader.fragmentShader.replace(
        'gl_FragColor = vec4( packNormalToRGB( normal ), opacity );',
        [
          'gl_FragColor = vec4( packNormalToRGB( normal ), opacity );',
          'gl_FragColor.a = 1.0 - pow( gl_FragCoord.z, 4.75 );',
          'gl_FragColor.r = 0.70;',
          'gl_FragColor.g = 0.85;',
          'gl_FragColor.b = 1.0;',
        ].join('\n'),
      )
    }

    m.needsUpdate = true
    return m
  }, [])

  const actions = useRef<GLTFActions>()
  //@ts-ignore
  const [mixer] = useState(() => new THREE.AnimationMixer(nodes.mesh))

  useEffect(() => {
    api.subscribe(
      (state: Transform | undefined | null) => {
        if (!state || !actions.current) {
          return
        }
        if (state.animation === currentAnimation.current) return

        const newAction = actions.current[state.animation]
        const previousAction = actions.current[currentAnimation.current]

        newAction.reset()
        newAction.setEffectiveTimeScale(1)

        const weight =
          state.animation === 'turn_left' || state.animation === 'turn_right' ? 0.5 : 1
        const time = state.animation === 'jump' ? 0 : 0.2

        newAction.setEffectiveWeight(weight)

        previousAction.crossFadeTo(newAction, time, true)
        currentAnimation.current = state.animation
      },
      (state) => state?.userTransforms?.[id],
    )
  }, [id])

  useEffect(() => {
    if (actions.current && currentAnimation.current) {
    }
  }, [currentAnimation.current])

  useFrame((_, delta) => {
    mixer.update(delta)
  })

  useEffect(() => {
    actions.current = {
      idle: mixer.clipAction(animations[0], group.current),
      jump: mixer.clipAction(animations[1], group.current),
      run_left: mixer.clipAction(animations[2], group.current),
      run_right: mixer.clipAction(animations[3], group.current),
      run: mixer.clipAction(animations[4], group.current),
      turn_left: mixer.clipAction(animations[5], group.current),
      turn_right: mixer.clipAction(animations[6], group.current),
      walk: mixer.clipAction(animations[7], group.current),
      walk_backwards: mixer.clipAction(animations[8], group.current),
      walk_left: mixer.clipAction(animations[9], group.current),
      walk_right: mixer.clipAction(animations[10], group.current),
    }

    Object.values(actions.current).forEach((a) => {
      a.setEffectiveWeight(0)
      a.enabled = true
      a.setEffectiveTimeScale(1)
      a.play()
    })

    if (currentAnimation.current) {
      actions.current[currentAnimation.current].play()
    }
  }, [group, animations, mixer])

  return (
    <group
      ref={group}
      {...rest}
      dispose={null}
      rotation={[0, -Math.PI, 0]}
      position={[0, -HEIGHT, 0]}
    >
      <group name="root" rotation={[0, -0.09, 0]}>
        <primitive object={nodes.mixamorigHips} />
        <skinnedMesh
          material={material} //@ts-ignore
          geometry={nodes.mesh.geometry} //@ts-ignore
          skeleton={nodes.mesh.skeleton}
        ></skinnedMesh>
      </group>
    </group>
  )
}

useGLTF.preload('/model/human.glb')

export default Human
