import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';
import { PIXELFORMAT_R32U, SEMANTIC_ATTR13, SEMANTIC_POSITION, CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { MeshInstance } from '../mesh-instance.js';
import { GSplatResolveSH } from './gsplat-resolve-sh.js';
import { GSplatSorter } from './gsplat-sorter.js';
import { GSplatSogsData } from './gsplat-sogs-data.js';
import { GSplatResourceBase } from './gsplat-resource-base.js';
import { ShaderMaterial } from '../materials/shader-material.js';
import { BLEND_NONE, BLEND_PREMULTIPLIED } from '../constants.js';

const mat = new Mat4();
const cameraPosition = new Vec3();
const cameraDirection = new Vec3();
const viewport = [
		0,
		0
];
class GSplatInstance {
		constructor(resource, options = {}){
				this.options = {};
				this.sorter = null;
				this.lastCameraPosition = new Vec3();
				this.lastCameraDirection = new Vec3();
				this.resolveSH = null;
				this.cameras = [];
				this.resource = resource;
				this.orderTexture = resource.createTexture('splatOrder', PIXELFORMAT_R32U, resource.evalTextureSize(resource.numSplats));
				if (options.material) {
						this._material = options.material;
						this._material.setParameter('splatOrder', this.orderTexture);
				} else {
						this._material = new ShaderMaterial({
								uniqueName: 'SplatMaterial',
								vertexGLSL: '#include "gsplatVS"',
								fragmentGLSL: '#include "gsplatPS"',
								vertexWGSL: '#include "gsplatVS"',
								fragmentWGSL: '#include "gsplatPS"',
								attributes: {
										vertex_position: SEMANTIC_POSITION,
										vertex_id_attrib: SEMANTIC_ATTR13
								}
						});
						this.configureMaterial(this._material);
						this._material.update();
				}
				this.meshInstance = new MeshInstance(resource.mesh, this._material);
				this.meshInstance.setInstancing(resource.instanceIndices, true);
				this.meshInstance.gsplatInstance = this;
				this.meshInstance.instancingCount = 0;
				const centers = resource.centers.slice();
				const chunks = resource.chunks?.slice();
				this.sorter = new GSplatSorter();
				this.sorter.init(this.orderTexture, centers, chunks);
				this.sorter.on('updated', (count)=>{
						this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize);
						this.material.setParameter('numSplats', count);
				});
				this.setHighQualitySH(options.highQualitySH ?? false);
		}
		destroy() {
				this.resolveSH?.destroy();
				this.material?.destroy();
				this.meshInstance?.destroy();
				this.sorter?.destroy();
		}
		set material(value) {
				if (this._material !== value) {
						this._material = value;
						this._material.setParameter('splatOrder', this.orderTexture);
						if (this.meshInstance) {
								this.meshInstance.material = value;
						}
				}
		}
		get material() {
				return this._material;
		}
		configureMaterial(material, options = {}) {
				this.resource.configureMaterial(material);
				material.setParameter('numSplats', 0);
				material.setParameter('splatOrder', this.orderTexture);
				material.setParameter('alphaClip', 0.3);
				material.setDefine(`DITHER_${options.dither ? 'BLUENOISE' : 'NONE'}`, '');
				material.cull = CULLFACE_NONE;
				material.blendType = options.dither ? BLEND_NONE : BLEND_PREMULTIPLIED;
				material.depthWrite = !!options.dither;
		}
		updateViewport(cameraNode) {
				const camera = cameraNode?.camera;
				const renderTarget = camera?.renderTarget;
				const { width, height } = renderTarget ?? this.resource.device;
				viewport[0] = width;
				viewport[1] = height;
				const xr = camera?.camera?.xr;
				if (xr?.active && xr.views.list.length === 2) {
						viewport[0] *= 0.5;
				}
				this.material.setParameter('viewport', viewport);
		}
		sort(cameraNode) {
				if (this.sorter) {
						const cameraMat = cameraNode.getWorldTransform();
						cameraMat.getTranslation(cameraPosition);
						cameraMat.getZ(cameraDirection);
						const modelMat = this.meshInstance.node.getWorldTransform();
						const invModelMat = mat.invert(modelMat);
						invModelMat.transformPoint(cameraPosition, cameraPosition);
						invModelMat.transformVector(cameraDirection, cameraDirection);
						if (!cameraPosition.equalsApprox(this.lastCameraPosition) || !cameraDirection.equalsApprox(this.lastCameraDirection)) {
								this.lastCameraPosition.copy(cameraPosition);
								this.lastCameraDirection.copy(cameraDirection);
								this.sorter.setCamera(cameraPosition, cameraDirection);
						}
				}
				this.updateViewport(cameraNode);
		}
		update() {
				if (this.cameras.length > 0) {
						const camera = this.cameras[0];
						this.sort(camera._node);
						this.resolveSH?.render(camera._node, this.meshInstance.node.getWorldTransform());
						this.cameras.length = 0;
				}
		}
		setHighQualitySH(value) {
				const { resource } = this;
				const { gsplatData } = resource;
				if (gsplatData instanceof GSplatSogsData && gsplatData.shBands > 0 && value === !!this.resolveSH) {
						if (this.resolveSH) {
								this.resolveSH.destroy();
								this.resolveSH = null;
						} else {
								this.resolveSH = new GSplatResolveSH(resource.device, this);
						}
				}
		}
}

export { GSplatInstance };
