import 'element-behaviors'
import {MeshStandardMaterial} from 'three/src/materials/MeshStandardMaterial.js'
import {booleanAttribute, numberAttribute, stringAttribute} from '@lume/element'
import {behavior} from '../../Behavior.js'
import {receiver} from '../../PropReceiver.js'
import {MaterialBehavior, type MaterialBehaviorAttributes} from './MaterialBehavior.js'

export type StandardMaterialBehaviorAttributes =
	| MaterialBehaviorAttributes
	| 'alphaMap'
	| 'aoMap'
	| 'aoMapIntensity'
	| 'bumpMap'
	| 'bumpScale'
	| 'displacementMap'
	| 'displacementScale'
	| 'displacementBias'
	| 'texture' // map
	| 'normalMap'
	| 'normalScale'
	| 'metalness'
	| 'metalnessMap'
	| 'morphNormals'
	| 'morphTargets'
	| 'roughness'
	| 'roughnessMap'
	| 'vertexTangents'

/**
 * @class StandardMaterialBehavior -
 *
 * A standard physically based material, using Metallic-Roughness workflow.
 *
 * Backed by Three.js [`THREE.MeshStandardMaterial`](https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial)
 *
 * @extends MaterialBehavior
 */
export
@behavior
class StandardMaterialBehavior extends MaterialBehavior {
	@stringAttribute @receiver alphaMap = ''
	@stringAttribute @receiver aoMap = ''
	@numberAttribute @receiver aoMapIntensity = 1
	@stringAttribute @receiver bumpMap = ''
	@numberAttribute @receiver bumpScale = 1
	@stringAttribute @receiver displacementMap = ''
	@numberAttribute @receiver displacementScale = 1
	@numberAttribute @receiver displacementBias = 0
	// emissive?: Color | string | number;
	// envMap?: Texture | null;
	// @numberAttribute @receiver envMapIntensity?: number
	// @numberAttribute @receiver emissiveIntensity?: number
	// emissiveMap?: Texture | null;
	// lightMap?: Texture | null;
	// @numberAttribute @receiver lightMapIntensity?: number
	@stringAttribute @receiver texture = '' // map
	@stringAttribute @receiver normalMap = ''
	// normalMapType
	@numberAttribute @receiver normalScale = 1
	@numberAttribute @receiver metalness = 0
	@stringAttribute @receiver metalnessMap = ''
	// @numberAttribute @receiver refractionRatio?: number
	@numberAttribute @receiver roughness = 1
	@stringAttribute @receiver roughnessMap = ''

	// wireframe?: boolean

	// @numberAttribute @receiver wireframeLinewidth?: number // Not supported because the WebGL line width is always 1.

	// @booleanAttribute @receiver skinning: boolean = false
	@booleanAttribute @receiver vertexTangents: boolean = false
	@booleanAttribute @receiver morphTargets: boolean = false
	@booleanAttribute @receiver morphNormals: boolean = false

	override _createComponent() {
		return new MeshStandardMaterial()
	}

	override connectedCallback() {
		super.connectedCallback()

		this.createEffect(() => {
			const mat = this.meshComponent
			if (!mat) return

			mat.aoMapIntensity = this.aoMapIntensity
			mat.bumpScale = this.bumpScale
			mat.displacementScale = this.displacementScale
			mat.displacementBias = this.displacementBias
			mat.normalScale.set(this.normalScale, this.normalScale)
			mat.metalness = this.metalness
			// mat.morphNormals = this.morphNormals
			// mat.morphTargets = this.morphTargets
			mat.roughness = this.roughness
			// mat.vertexTangents = this.vertexTangents

			// TODO Needed?
			// mat.needsUpdate = true

			this.element.needsUpdate()
		})

		this._handleTexture(
			() => this.alphaMap,
			(mat, tex) => (mat.alphaMap = tex),
			mat => !!mat.alphaMap,
		)
		this._handleTexture(
			() => this.aoMap,
			(mat, tex) => (mat.aoMap = tex),
			mat => !!mat.aoMap,
		)
		this._handleTexture(
			() => this.bumpMap,
			(mat, tex) => (mat.bumpMap = tex),
			mat => !!mat.bumpMap,
		)
		this._handleTexture(
			() => this.displacementMap,
			(mat, tex) => (mat.displacementMap = tex),
			mat => !!mat.displacementMap,
		)
		this._handleTexture(
			() => this.texture, // map
			(mat, tex) => (mat.map = tex),
			mat => !!mat.map,
			() => {},
			true,
		)
		this._handleTexture(
			() => this.normalMap,
			(mat, tex) => (mat.normalMap = tex),
			mat => !!mat.normalMap,
		)
		this._handleTexture(
			() => this.metalnessMap,
			(mat, tex) => (mat.metalnessMap = tex),
			mat => !!mat.metalnessMap,
		)
		this._handleTexture(
			() => this.roughnessMap,
			(mat, tex) => (mat.roughnessMap = tex),
			mat => !!mat.roughnessMap,
		)
	}
}

if (globalThis.window?.document && !elementBehaviors.has('standard-material'))
	elementBehaviors.define('standard-material', StandardMaterialBehavior)

// This prevents errors with mixins. https://discord.com/channels/508357248330760243/508357248330760249/954526657312604180
export type MixinBaseClass<T> = T extends new (..._: any) => infer I
	? {[K in keyof T]: T[K]} & (new (...args: any[]) => I)
	: new (...args: any[]) => T
