import {ParamConfig} from '../../engine/nodes/utils/params/ParamsConfig';
import {Constructor, Number3} from '../../types/GlobalTypes';
// import {ColorConversion} from '../Color';
import {
	Group,
	Mesh,
	Color,
	Vector2,
	DirectionalLight,
	LineBasicMaterial,
	BufferGeometry,
	Float32BufferAttribute,
	Line,
} from 'three';
import {DefaultOperationParams} from '../operations/_Base';
// import {CameraHelper} from 'three';
import {CoreCameraHelper} from '../helpers/CoreCameraHelper';
import {ObjectType, registerObjectType} from '../geometry/Constant';
export interface DirectionalLightParams extends DefaultOperationParams {
	color: Color;
	intensity: number;
	distance: number;
	//
	showHelper: boolean;
	name: string;
	//
	castShadow: boolean;
	shadowAutoUpdate: boolean;
	shadowUpdateOnNextRender: boolean;
	shadowRes: Vector2;
	shadowSize: Vector2;
	shadowBias: number;
	shadowRadius: number;
	// debugShadow: boolean; // removed (see spotlight for explanation)
	//
	raymarchingPenumbra: number;
	raymarchingShadowBiasAngle: number;
	raymarchingShadowBiasDistance: number;
}

export const DEFAULT_DIRECTIONAL_LIGHT_PARAMS: DirectionalLightParams = {
	color: new Color(1, 1, 1),
	intensity: 1,
	distance: 100,
	//
	showHelper: false,
	name: 'directionalLight',
	//
	castShadow: false,
	shadowAutoUpdate: true,
	shadowUpdateOnNextRender: false,
	shadowRes: new Vector2(1024, 1024),
	shadowSize: new Vector2(2, 2),
	shadowBias: 0.001,
	shadowRadius: 0,
	debugShadow: false,
	//
	raymarchingPenumbra: 0, // keep as 0 by default since it's more performant
	raymarchingShadowBiasAngle: 0.01,
	raymarchingShadowBiasDistance: 0.1,
};
const DEFAULT = DEFAULT_DIRECTIONAL_LIGHT_PARAMS;

export function DirectionalLightParamConfig<TBase extends Constructor>(Base: TBase) {
	return class Mixin extends Base {
		light = ParamConfig.FOLDER();
		/** @param light color */
		color = ParamConfig.COLOR(DEFAULT.color.toArray() as Number3, {
			// conversion: ColorConversion.SRGB_TO_LINEAR,
		});
		/** @param light intensity */
		intensity = ParamConfig.FLOAT(DEFAULT.intensity, {
			range: [0, 2],
			rangeLocked: [true, false],
		});
		/** @param light distance */
		distance = ParamConfig.FLOAT(DEFAULT.distance, {
			range: [0, 100],
			rangeLocked: [true, false],
		});
		// helper
		/** @param toggle to show helper */
		showHelper = ParamConfig.BOOLEAN(DEFAULT.showHelper);
		/** @param light name */
		name = ParamConfig.STRING('`$OS`');

		// shadows
		shadow = ParamConfig.FOLDER();
		/** @param toggle on to cast shadows */
		castShadow = ParamConfig.BOOLEAN(DEFAULT.castShadow);
		/** @param toggle off if the shadows do not need to be regenerated */
		shadowAutoUpdate = ParamConfig.BOOLEAN(DEFAULT.shadowAutoUpdate, {
			visibleIf: {castShadow: 1},
		});
		/** @param press button to update the shadows on next render */
		shadowUpdateOnNextRender = ParamConfig.BOOLEAN(DEFAULT.shadowUpdateOnNextRender, {
			visibleIf: {castShadow: 1, shadowAutoUpdate: 0},
		});
		/** @param shadow resolution */
		shadowRes = ParamConfig.VECTOR2(DEFAULT.shadowRes.toArray(), {
			visibleIf: {castShadow: true},
		});
		/** @param shadow size */
		shadowSize = ParamConfig.VECTOR2(DEFAULT.shadowSize.toArray(), {
			visibleIf: {castShadow: true},
		});
		/** @param shadow bias */
		shadowBias = ParamConfig.FLOAT(DEFAULT.shadowBias, {
			visibleIf: {castShadow: true},
			range: [-0.01, 0.01],
			rangeLocked: [false, false],
		});
		/** @param shadows radius. This only has effect when setting the ROP/WebGLRenderer's shadowMapType to VSM */
		shadowRadius = ParamConfig.FLOAT(DEFAULT.shadowRadius, {
			visibleIf: {castShadow: 1},
			range: [0, 10],
			rangeLocked: [true, false],
		});
		/** @param display shadow on a plane behind the light */
		// debugShadow = ParamConfig.BOOLEAN(DEFAULT.debugShadow, {
		// 	visibleIf: {castShadow: 1},
		// });
		// raymarching
		raymarching = ParamConfig.FOLDER();
		/** @param this affects the shadows cast inside raymarchingBuilder materials */
		raymarchingPenumbra = ParamConfig.FLOAT(DEFAULT.raymarchingPenumbra);
		/** @param shadow bias */
		raymarchingShadowBiasAngle = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasAngle, {
			range: [0, 1],
			rangeLocked: [true, false],
		});
		/** @param shadow bias */
		raymarchingShadowBiasDistance = ParamConfig.FLOAT(DEFAULT.raymarchingShadowBiasDistance, {
			range: [0, 1],
			rangeLocked: [true, false],
		});
	};
}

export interface DirectionalLightContainerParams {
	showHelper: boolean;
}
export class DirectionalLightContainer extends Group {
	private _light = new DirectionalLight();
	private _target = this._light.target;
	public showHelper = false;
	public override matrixAutoUpdate = false;
	constructor(options: DirectionalLightContainerParams, public readonly nodeName: string) {
		super();
		registerObjectType({
			type: ObjectType.DIRECTIONAL_LIGHT,
			checkFunc: (o) => {
				if ((o as DirectionalLight).isDirectionalLight) {
					return ObjectType.DIRECTIONAL_LIGHT;
				}
			},
			ctor: DirectionalLight,
			humanName: 'DirectionalLight',
		});
		this.showHelper = options.showHelper;
		// set light pos to 0,0,1
		// in order to have it face z axis
		this._light.position.set(0, 0, 1);
		this._light.updateMatrix();
		this._target.updateMatrix();
		this._light.matrixAutoUpdate = false;
		this._target.matrixAutoUpdate = false;
		this.name = `DirectionalLightContainer_${nodeName}`;
		this._light.name = `DirectionalLight_${nodeName}`;
		this._target.name = `DirectionalLightTarget_${nodeName}`;

		this.add(this._light);
		this.add(this._target);
		this.updateHelper();
	}

	light() {
		return this._light;
	}
	override copy(source: this, recursive?: boolean): this {
		this._light.copy(source.light(), false);
		super.copy(source, false);

		this._light.target = this._target;
		this.updateHelper();

		return this as this;
	}

	override clone(recursive?: boolean): this {
		const cloned = new DirectionalLightContainer({showHelper: this.showHelper}, this.nodeName);
		cloned.copy(this);

		return cloned as this;
	}

	updateHelper() {
		if (this.showHelper) {
			this.__helper__ = this.__helper__ || new CoreDirectionalLightHelper(this);
			this.add(this.__helper__.object);
			this.__helper__.update();
		} else {
			if (this.__helper__) {
				this.remove(this.__helper__.object);
			}
		}
	}

	private __helper__: CoreDirectionalLightHelper | undefined;
}

export class CoreDirectionalLightHelper {
	public object: Mesh = new Mesh();
	private _lineMaterial = new LineBasicMaterial();
	private _cameraHelper!: CoreCameraHelper;
	private _square = new Line();

	constructor(public container: DirectionalLightContainer) {
		this.createAndBuildObject();
	}

	createAndBuildObject() {
		this.buildHelper();
		this.update();
	}

	buildHelper() {
		const light = this.container.light();
		const geometry = new BufferGeometry();
		const size = 1;
		geometry.setAttribute(
			'position',
			new Float32BufferAttribute(
				[-size, size, 0, size, size, 0, size, -size, 0, -size, -size, 0, -size, size, 0],
				3
			)
		);

		this._square.geometry = geometry;
		this._square.material = this._lineMaterial;
		// this._square.rotateX(Math.PI * 0.5);
		this._square.updateMatrix();
		this._square.matrixAutoUpdate = false;

		this.object.add(this._square);

		this._cameraHelper = new CoreCameraHelper(light.shadow.camera);
		// this._cameraHelper.rotateX(-Math.PI * 0.5);
		this._cameraHelper.updateMatrix();
		this._cameraHelper.matrixAutoUpdate = false;
		this.object.add(this._cameraHelper);

		this.object.name = `CoreDirectionalLightHelper_${this.container.nodeName}`;
		this._square.name = `CoreDirectionalLightHelperSquare_${this.container.nodeName}`;
		this._cameraHelper.name = `CoreDirectionalLightHelperCameraHelper_${this.container.nodeName}`;
	}

	update() {
		this.object.updateMatrix();
		this._cameraHelper.update();

		this._lineMaterial.color.copy(this.container.light().color);
	}
}
