import { ComponentPropsOptions, ComponentPublicInstance, defineComponent, InjectionKey, watch } from 'vue'
import { BufferGeometry, Material, Mesh as TMesh } from 'three'
import Object3D, { Object3DSetupInterface } from '../core/Object3D'
import { bindProp } from '../tools'

export interface MeshSetupInterface extends Object3DSetupInterface {
  mesh?: TMesh
  geometry?: BufferGeometry
  material?: Material
  loading?: boolean
}

export interface MeshInterface extends MeshSetupInterface {
  setGeometry(g: BufferGeometry): void
  setMaterial(m: Material): void
}

export interface MeshPublicInterface extends ComponentPublicInstance, MeshInterface {}

export const MeshInjectionKey: InjectionKey<MeshPublicInterface> = Symbol('Mesh')

const Mesh = defineComponent({
  name: 'Mesh',
  extends: Object3D,
  props: {
    castShadow: Boolean,
    receiveShadow: Boolean,
  },
  setup(): MeshSetupInterface {
    return {}
  },
  provide() {
    return {
      [MeshInjectionKey as symbol]: this,
    }
  },
  mounted() {
    // TODO : proper ?
    if (!this.mesh && !this.loading) this.initMesh()
  },
  methods: {
    initMesh() {
      const mesh = new TMesh(this.geometry, this.material)

      bindProp(this, 'castShadow', mesh)
      bindProp(this, 'receiveShadow', mesh)

      this.mesh = mesh
      this.initObject3D(mesh)
    },
    createGeometry() {},
    addGeometryWatchers(props: Readonly<ComponentPropsOptions>) {
      Object.keys(props).forEach(prop => {
        // @ts-ignore
        watch(() => this[prop], () => {
          this.refreshGeometry()
        })
      })
    },
    setGeometry(geometry: BufferGeometry) {
      this.geometry = geometry
      if (this.mesh) this.mesh.geometry = geometry
    },
    setMaterial(material: Material) {
      this.material = material
      if (this.mesh) this.mesh.material = material
    },
    refreshGeometry() {
      const oldGeo = this.geometry
      this.createGeometry()
      if (this.mesh && this.geometry) this.mesh.geometry = this.geometry
      oldGeo?.dispose()
    },
  },
  unmounted() {
    // for predefined mesh (geometry/material are not unmounted)
    if (this.geometry) this.geometry.dispose()
    if (this.material) this.material.dispose()
  },
  __hmrId: 'Mesh',
})

export default Mesh

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function meshComponent<P extends Readonly<ComponentPropsOptions>>(
  name: string,
  props: P,
  createGeometry: {(c: any): BufferGeometry}
) {
  return defineComponent({
    name,
    extends: Mesh,
    props,
    created() {
      this.createGeometry()
      this.addGeometryWatchers(props)
    },
    methods: {
      createGeometry() {
        this.geometry = createGeometry(this)
      },
    },
  })
}