import {Constructor} from '../../../../../types/GlobalTypes';
import {BaseNodeType} from '../../../_Base';
import {ParamConfig} from '../../../utils/params/ParamsConfig';
import {NodeContext} from '../../../../poly/NodeContext';
import {RootManagerNode} from '../../Root';
// import {ColorConversion} from '../../../../../core/Color';
import {Color} from 'three';
import {TypeAssert} from '../../../../poly/Assert';

export enum BackgroundMode {
	NONE = 'none',
	COLOR = 'color',
	TEXTURE = 'texture',
}
export const BACKGROUND_MODES: BackgroundMode[] = [BackgroundMode.NONE, BackgroundMode.COLOR, BackgroundMode.TEXTURE];

const CallbackOptions = {
	cook: false,
	callback: (node: BaseNodeType) => {
		SceneBackgroundController.update(node as RootManagerNode);
	},
};

export function SceneBackgroundParamConfig<TBase extends Constructor>(Base: TBase) {
	return class Mixin extends Base {
		// background
		/** @param set background mode (none, color or texture). Note that in order to have a transparent background, you also need to set the renderer's alpha to true. In order to do so, you may need to create a rop/WebGLRenderer node, set it alpha parameter, and assign the node to your camera. */
		backgroundMode = ParamConfig.INTEGER(BACKGROUND_MODES.indexOf(BackgroundMode.COLOR), {
			menu: {
				entries: BACKGROUND_MODES.map((mode, i) => {
					return {name: mode, value: i};
				}),
			},
			...CallbackOptions,
			separatorBefore: true,
		});
		/** @param background color */
		bgColor = ParamConfig.COLOR([0.01, 0.01, 0.01], {
			visibleIf: {backgroundMode: BACKGROUND_MODES.indexOf(BackgroundMode.COLOR)},
			...CallbackOptions,
			// conversion: ColorConversion.SRGB_TO_LINEAR,
		});
		/** @param background texture */
		bgTexture = ParamConfig.NODE_PATH('', {
			visibleIf: {backgroundMode: BACKGROUND_MODES.indexOf(BackgroundMode.TEXTURE)},
			nodeSelection: {
				context: NodeContext.COP,
			},
			// dependentOnFoundNode: false,
			...CallbackOptions,
		});
		/** @param background blur */
		bgBlur = ParamConfig.FLOAT(0, {
			visibleIf: {backgroundMode: BACKGROUND_MODES.indexOf(BackgroundMode.TEXTURE)},
			range: [0, 1],
			rangeLocked: [true, false],
			...CallbackOptions,
		});
		/** @param background intensity */
		bgIntensity = ParamConfig.FLOAT(1, {
			visibleIf: {backgroundMode: BACKGROUND_MODES.indexOf(BackgroundMode.TEXTURE)},
			range: [0, 2],
			rangeLocked: [true, false],
			...CallbackOptions,
		});
	};
}

const CALLBACK_NAME = 'SceneBackgroundController';
export class SceneBackgroundController {
	constructor(protected node: RootManagerNode) {}
	addHooks() {
		const p = this.node.p;
		const params = [p.backgroundMode, p.bgColor, p.bgTexture];
		for (const param of params) {
			param.addPostDirtyHook(CALLBACK_NAME, this._updateBound);
		}
	}

	setMode(mode: BackgroundMode) {
		this.node.p.backgroundMode.set(BACKGROUND_MODES.indexOf(mode));
	}
	backgroundMode(): BackgroundMode {
		return BACKGROUND_MODES[this.node.pv.backgroundMode];
	}
	private _updateBound = this.update.bind(this);
	async update() {
		const backgroundMode = this.backgroundMode();
		switch (backgroundMode) {
			case BackgroundMode.NONE: {
				return this._setBackgroundNone();
			}
			case BackgroundMode.COLOR: {
				return await this._setBackgroundColor();
			}
			case BackgroundMode.TEXTURE: {
				return await this._setBackgroundTexture();
			}
		}
		TypeAssert.unreachable(backgroundMode);
	}

	private _setBackgroundNone() {
		const scene = this.node.object;
		scene.background = null;
	}
	private async _setBackgroundColor() {
		const scene = this.node.object;
		const pv = this.node.pv;
		await this.node.p.bgColor.compute();
		if (scene.background && scene.background instanceof Color) {
			scene.background.copy(pv.bgColor);
		} else {
			scene.background = pv.bgColor;
		}
	}
	private async _setBackgroundTexture() {
		const scene = this.node.object;
		const pv = this.node.pv;
		const node = pv.bgTexture.nodeWithContext(NodeContext.COP);
		if (node) {
			const container = await node.compute();
			scene.background = container.texture();
		} else {
			this.node.states.error.set('bgTexture node not found');
			scene.background = null;
		}
		scene.backgroundBlurriness = pv.bgBlur;
		scene.backgroundIntensity = pv.bgIntensity;
	}

	static update(node: RootManagerNode) {
		node.sceneBackgroundController.update();
	}
}
