/**
 * Creates a light map
 *
 *
 */

// https://threejs.org/examples/?q=light#webgl_shadowmap_progressive
import {TypedCopNode} from './_Base';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {BaseNodeType} from '../_Base';
import {Object3D, WebGLRenderer} from 'three';
import {Light} from 'three';
import {LightMapController} from './utils/LightMapController';
// import {DEFAULT_ITERATION_BLEND} from './utils/lightMap/Common';
import {Mesh} from 'three';
import {isBooleanTrue} from '../../../core/BooleanValue';
import {DataTextureController, DataTextureControllerBufferType} from './utils/DataTextureController';
import {CopRendererController} from './utils/RendererController';
import {CopType} from '../../poly/registers/nodes/types/Cop';

class LightMapCopParamConfig extends NodeParamsConfig {
	/** @param click to update shadow, when mode is manual */
	update = ParamConfig.BUTTON(null, {
		callback: (node: BaseNodeType) => {
			LightMapCopNode.PARAM_CALLBACK_updateManual(node as LightMapCopNode);
		},
	});
	/** @param defines if the shader is rendered via the same camera used to render the scene */
	useCameraRenderer = ParamConfig.BOOLEAN(1); // needs to be 1, as it does not work on firefox otherwise
	/** @param shadow resolution */
	lightMapRes = ParamConfig.INTEGER(1024, {range: [1, 2048], rangeLocked: [true, false]});
	/** @param iterations */
	iterations = ParamConfig.INTEGER(512, {range: [1, 2048], rangeLocked: [true, false]});
	/** @param blendWindow */
	// iterationBlend = ParamConfig.FLOAT(DEFAULT_ITERATION_BLEND, {
	// 	range: [0, 1],
	// 	rangeLocked: [true, true],
	// });
	/** @param blurEdges */
	blur = ParamConfig.BOOLEAN(1);
	/** @param blurAmount */
	blurAmount = ParamConfig.FLOAT(1, {
		visibleIf: {blur: 1},
		range: [0, 1],
		rangeLocked: [true, false],
	});
	/** @param lightPositionVariation */
	lightRadius = ParamConfig.FLOAT(1, {
		range: [0, 10],
	});

	objectsMask = ParamConfig.STRING('', {objectMask: true});
	lightsMask = ParamConfig.STRING('*', {objectMask: true});
}
const ParamsConfig = new LightMapCopParamConfig();

export class LightMapCopNode extends TypedCopNode<LightMapCopParamConfig> {
	override paramsConfig = ParamsConfig;
	static override type(): Readonly<CopType.LIGHT_MAP> {
		return CopType.LIGHT_MAP;
	}
	private lightMapController: LightMapController | undefined;
	private _includedObjects: Mesh[] = [];
	private _includedLights: Light[] = [];
	private _dataTextureController: DataTextureController | undefined;
	private _rendererController: CopRendererController | undefined;

	override async cook() {
		await this._render();
	}

	private async _createLightMapController() {
		const renderer = await this.scene().renderersRegister.waitForRenderer();
		if (!renderer) {
			console.warn('no renderer found');
			return;
		}
		if (!(renderer instanceof WebGLRenderer)) {
			this.states.error.set('renderer found is not WebGLRenderer');
			return;
		}
		const lightMapController = new LightMapController(renderer);
		return lightMapController;
	}

	//
	//
	// ACTIVE
	//
	//
	// static PARAM_CALLBACK_update_updateMode(node: LightMapCopNode) {
	// 	// node._updateRenderHook();
	// }

	//
	//
	// UPDATE
	//
	//
	private async _render() {
		this.lightMapController = this.lightMapController || (await this._createLightMapController());
		if (!this.lightMapController) {
			return;
		}
		const mainCamera = await this.scene().mainCamera();
		if (!mainCamera) {
			return;
		}
		this._updateObjectsAndLightsList();
		this.lightMapController.setState(this._includedObjects, this._includedLights);

		// const camera = mainCameraNode.camera();
		this.lightMapController.setParams({
			resolution: this.pv.lightMapRes,
			lightRadius: this.pv.lightRadius,
			totalIterationsCount: this.pv.iterations,
			// iterationBlend: this.pv.iterationBlend,
			blur: this.pv.blur,
			blurAmount: this.pv.blurAmount,
		});
		this.lightMapController.runUpdates(mainCamera);
		this.lightMapController.restoreState();
		// this.setTexture();

		const renderTarget = this.lightMapController.textureRenderTarget();
		if (isBooleanTrue(this.pv.useCameraRenderer)) {
			this.setTexture(renderTarget.texture);
		} else {
			this._dataTextureController =
				this._dataTextureController || new DataTextureController(DataTextureControllerBufferType.Float32Array);
			this._rendererController = this._rendererController || new CopRendererController(this);
			const renderer = await this._rendererController.waitForRenderer();
			if (!(renderer instanceof WebGLRenderer)) {
				this.states.error.set('renderer found is not WebGLRenderer');
				this.cookController.endCook();
				return;
			}
			const texture = this._dataTextureController.fromRenderTarget(renderer, renderTarget);
			this.setTexture(texture);
		}
	}
	static PARAM_CALLBACK_updateManual(node: LightMapCopNode) {
		node.setDirty();
	}

	//
	//
	// UPDATE OBJECTS LIST
	//
	//
	private _updateObjectsAndLightsList() {
		let matchedObjects: Object3D[] = [];
		let matchedLights: Object3D[] = [];
		this._includedLights = [];
		this._includedObjects = [];
		const lightsByUuid: WeakSet<Object3D> = new WeakSet();
		if (this.pv.lightsMask != '') {
			matchedLights = this.scene().objectsByMask(this.pv.lightsMask) as Object3D[];
			for (const matchedLight of matchedLights) {
				if (matchedLight instanceof Light) {
					this._includedLights.push(matchedLight);
					lightsByUuid.add(matchedLight);
				}
			}
		}
		if (this.pv.objectsMask != '') {
			matchedObjects = this.scene().objectsByMask(this.pv.objectsMask) as Object3D[];
			for (const matchedObject of matchedObjects) {
				if (!(matchedObject instanceof Light)) {
					if (!lightsByUuid.has(matchedObject) && matchedObject instanceof Mesh) {
						this._includedObjects.push(matchedObject);
					}
				}
			}
		}
	}
}
