import { LAYERID_WORLD } from '../../../scene/constants.js';
import { GSplatInstance } from '../../../scene/gsplat/gsplat-instance.js';
import { Asset } from '../../asset/asset.js';
import { AssetReference } from '../../asset/asset-reference.js';
import { Component } from '../component.js';
import { GSplatPlacement } from '../../../scene/gsplat-unified/gsplat-placement.js';

class GSplatComponent extends Component {
		constructor(system, entity){
				super(system, entity), this._layers = [
						LAYERID_WORLD
				], this._instance = null, this._placement = null, this._materialTmp = null, this._highQualitySH = true, this._customAabb = null, this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null, this._castShadows = false, this._unified = false;
				this._assetReference = new AssetReference('asset', this, system.app.assets, {
						add: this._onGSplatAssetAdded,
						load: this._onGSplatAssetLoad,
						remove: this._onGSplatAssetRemove,
						unload: this._onGSplatAssetUnload
				}, this);
				entity.on('remove', this.onRemoveChild, this);
				entity.on('removehierarchy', this.onRemoveChild, this);
				entity.on('insert', this.onInsertChild, this);
				entity.on('inserthierarchy', this.onInsertChild, this);
		}
		set customAabb(value) {
				this._customAabb = value;
				this._instance?.meshInstance?.setCustomAabb(this._customAabb);
				if (this._placement && this._customAabb) {
						this._placement.aabb = this._customAabb;
				}
		}
		get customAabb() {
				return this._customAabb;
		}
		set instance(value) {
				this.destroyInstance();
				this._instance = value;
				if (this._instance) {
						const mi = this._instance.meshInstance;
						if (!mi.node) {
								mi.node = this.entity;
						}
						mi.castShadow = this._castShadows;
						mi.setCustomAabb(this._customAabb);
						if (this.enabled && this.entity.enabled) {
								this.addToLayers();
						}
				}
		}
		get instance() {
				return this._instance;
		}
		set material(value) {
				if (this._instance) {
						this._instance.material = value;
				} else {
						this._materialTmp = value;
				}
		}
		get material() {
				return this._instance?.material ?? this._materialTmp ?? null;
		}
		set highQualitySH(value) {
				if (value !== this._highQualitySH) {
						this._highQualitySH = value;
						this._instance?.setHighQualitySH(value);
				}
		}
		get highQualitySH() {
				return this._highQualitySH;
		}
		set castShadows(value) {
				if (this._castShadows !== value) {
						const mi = this.instance?.meshInstance;
						if (mi) {
								const layers = this.layers;
								const scene = this.system.app.scene;
								if (this._castShadows && !value) {
										for(let i = 0; i < layers.length; i++){
												const layer = scene.layers.getLayerById(this.layers[i]);
												if (layer) {
														layer.removeShadowCasters([
																mi
														]);
												}
										}
								}
								mi.castShadow = value;
								if (!this._castShadows && value) {
										for(let i = 0; i < layers.length; i++){
												const layer = scene.layers.getLayerById(layers[i]);
												if (layer) {
														layer.addShadowCasters([
																mi
														]);
												}
										}
								}
						}
						this._castShadows = value;
				}
		}
		get castShadows() {
				return this._castShadows;
		}
		set unified(value) {
				if (this.enabled && this.entity.enabled) {
						return;
				}
				this._unified = value;
				this._onGSplatAssetAdded();
		}
		get unified() {
				return this._unified;
		}
		set layers(value) {
				this.removeFromLayers();
				this._layers.length = 0;
				for(let i = 0; i < value.length; i++){
						this._layers[i] = value[i];
				}
				if (!this.enabled || !this.entity.enabled) {
						return;
				}
				this.addToLayers();
		}
		get layers() {
				return this._layers;
		}
		set asset(value) {
				const id = value instanceof Asset ? value.id : value;
				if (this._assetReference.id === id) return;
				if (this._assetReference.asset && this._assetReference.asset.resource) {
						this._onGSplatAssetRemove();
				}
				this._assetReference.id = id;
				if (this._assetReference.asset) {
						this._onGSplatAssetAdded();
				}
		}
		get asset() {
				return this._assetReference.id;
		}
		destroyInstance() {
				if (this._placement) {
						this.removeFromLayers();
						this._placement = null;
				}
				if (this._instance) {
						this.removeFromLayers();
						this._instance?.destroy();
						this._instance = null;
				}
		}
		addToLayers() {
				if (this._placement) {
						const layers = this.system.app.scene.layers;
						for(let i = 0; i < this._layers.length; i++){
								layers.getLayerById(this._layers[i])?.addGSplatPlacement(this._placement);
						}
						return;
				}
				const meshInstance = this.instance?.meshInstance;
				if (meshInstance) {
						const layers = this.system.app.scene.layers;
						for(let i = 0; i < this._layers.length; i++){
								layers.getLayerById(this._layers[i])?.addMeshInstances([
										meshInstance
								]);
						}
				}
		}
		removeFromLayers() {
				if (this._placement) {
						const layers = this.system.app.scene.layers;
						for(let i = 0; i < this._layers.length; i++){
								layers.getLayerById(this._layers[i])?.removeGSplatPlacement(this._placement);
						}
						return;
				}
				const meshInstance = this.instance?.meshInstance;
				if (meshInstance) {
						const layers = this.system.app.scene.layers;
						for(let i = 0; i < this._layers.length; i++){
								layers.getLayerById(this._layers[i])?.removeMeshInstances([
										meshInstance
								]);
						}
				}
		}
		onRemoveChild() {
				this.removeFromLayers();
		}
		onInsertChild() {
				if (this.enabled && this.entity.enabled) {
						if (this._instance || this._placement) {
								this.addToLayers();
						}
				}
		}
		onRemove() {
				this.destroyInstance();
				this.asset = null;
				this._assetReference.id = null;
				this.entity.off('remove', this.onRemoveChild, this);
				this.entity.off('insert', this.onInsertChild, this);
		}
		onLayersChanged(oldComp, newComp) {
				this.addToLayers();
				oldComp.off('add', this.onLayerAdded, this);
				oldComp.off('remove', this.onLayerRemoved, this);
				newComp.on('add', this.onLayerAdded, this);
				newComp.on('remove', this.onLayerRemoved, this);
		}
		onLayerAdded(layer) {
				const index = this.layers.indexOf(layer.id);
				if (index < 0) return;
				if (this._instance) {
						layer.addMeshInstances(this._instance.meshInstance);
				}
		}
		onLayerRemoved(layer) {
				const index = this.layers.indexOf(layer.id);
				if (index < 0) return;
				if (this._instance) {
						layer.removeMeshInstances(this._instance.meshInstance);
				}
		}
		onEnable() {
				const scene = this.system.app.scene;
				const layers = scene.layers;
				this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this);
				if (layers) {
						this._evtLayerAdded = layers.on('add', this.onLayerAdded, this);
						this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this);
				}
				if (this._instance || this._placement) {
						this.addToLayers();
				} else if (this.asset) {
						this._onGSplatAssetAdded();
				}
		}
		onDisable() {
				const scene = this.system.app.scene;
				const layers = scene.layers;
				this._evtLayersChanged?.off();
				this._evtLayersChanged = null;
				if (layers) {
						this._evtLayerAdded?.off();
						this._evtLayerAdded = null;
						this._evtLayerRemoved?.off();
						this._evtLayerRemoved = null;
				}
				this.removeFromLayers();
		}
		hide() {
				if (this._instance) {
						this._instance.meshInstance.visible = false;
				}
		}
		show() {
				if (this._instance) {
						this._instance.meshInstance.visible = true;
				}
		}
		_onGSplatAssetAdded() {
				if (!this._assetReference.asset) {
						return;
				}
				if (this._assetReference.asset.resource) {
						this._onGSplatAssetLoad();
				} else if (this.enabled && this.entity.enabled) {
						this.system.app.assets.load(this._assetReference.asset);
				}
		}
		_onGSplatAssetLoad() {
				this.destroyInstance();
				const asset = this._assetReference.asset;
				if (this.unified) {
						this._placement = null;
						if (asset) {
								this._placement = new GSplatPlacement(asset.resource, this.entity);
						}
				} else {
						if (asset) {
								this.instance = new GSplatInstance(asset.resource, {
										material: this._materialTmp,
										highQualitySH: this._highQualitySH
								});
								this._materialTmp = null;
						}
				}
				if (asset) {
						this.customAabb = asset.resource.aabb.clone();
				}
		}
		_onGSplatAssetUnload() {
				this.destroyInstance();
		}
		_onGSplatAssetRemove() {
				this._onGSplatAssetUnload();
		}
}

export { GSplatComponent };
