import {TypedNode} from '../../../nodes/_Base';
import {Vector2} from 'three/src/math/Vector2';
import {JsonImportDispatcher} from './Dispatcher';
import {ParamType} from '../../../poly/ParamType';
import {ParamsUpdateOptions} from '../../../nodes/utils/params/ParamsController';
import {SceneJsonImporter} from '../../../io/json/import/Scene';
import {NodeContext} from '../../../poly/NodeContext';
import {NodeJsonExporterData, NodeJsonExporterUIData, InputData, IoConnectionPointsData} from '../export/Node';
import {
	ParamJsonExporterData,
	SimpleParamJsonExporterData,
	ComplexParamJsonExporterData,
} from '../../../nodes/utils/io/IOController';
import {NodesJsonImporter} from './Nodes';
import {Poly} from '../../../Poly';
import {CoreType} from '../../../../core/Type';
import {ObjectUtils} from '../../../../core/ObjectUtils';
import {CoreString} from '../../../../core/String';
import {PolyDictionary} from '../../../../types/GlobalTypes';

const COMPLEX_PARAM_DATA_KEYS: Readonly<string[]> = ['overriden_options', 'type'];

type BaseNodeTypeWithIO = TypedNode<NodeContext, any>;
export class NodeJsonImporter<T extends BaseNodeTypeWithIO> {
	constructor(protected _node: T) {}

	process_data(scene_importer: SceneJsonImporter, data: NodeJsonExporterData) {
		this.set_connection_points(data['connection_points']);

		// rather than having the children creation dependent on the persisted config and player mode, use the childrenAllowed() method
		// const skip_create_children = Poly.playerMode() && data.persisted_config;
		if (this._node.childrenAllowed()) {
			this.create_nodes(scene_importer, data['nodes']);
		}
		this.set_selection(data['selection']);

		// inputs clone
		if (this._node.io.inputs.override_cloned_state_allowed()) {
			const override = data['cloned_state_overriden'];
			if (override) {
				this._node.io.inputs.override_cloned_state(override);
			}
		}

		this.set_flags(data);

		// params
		// const spare_params_data = ParamJsonImporter.spare_params_data(data['params']);
		// this.set_params(spare_params_data);
		this.set_params(data['params']);

		if (data.persisted_config) {
			this.set_persisted_config(data.persisted_config);
		}

		this.from_data_custom(data);

		// already called in create_node()
		// this._node.lifecycle.set_creation_completed();
	}
	process_inputs_data(data: NodeJsonExporterData) {
		this.setInputs(data['inputs']);
	}

	process_ui_data(scene_importer: SceneJsonImporter, data: NodeJsonExporterUIData) {
		if (!data) {
			return;
		}
		if (Poly.playerMode()) {
			return;
		}
		const ui_data = this._node.uiData;
		const pos = data['pos'];
		if (pos) {
			const vector = new Vector2().fromArray(pos);
			ui_data.setPosition(vector);
		}
		const comment = data['comment'];
		if (comment) {
			ui_data.setComment(comment);
		}
		if (this._node.childrenAllowed()) {
			this.process_nodes_ui_data(scene_importer, data['nodes']);
		}
	}

	create_nodes(scene_importer: SceneJsonImporter, data?: PolyDictionary<NodeJsonExporterData>) {
		if (!data) {
			return;
		}
		const nodes_importer = new NodesJsonImporter(this._node);
		nodes_importer.process_data(scene_importer, data);
	}
	set_selection(data?: string[]) {
		if (this._node.childrenAllowed() && this._node.childrenController) {
			if (data && data.length > 0) {
				const selected_nodes: BaseNodeTypeWithIO[] = [];
				data.forEach((node_name) => {
					const node = this._node.node(node_name);
					if (node) {
						selected_nodes.push(node);
					}
				});
				this._node.childrenController.selection.set(selected_nodes);
			}
		}
	}

	set_flags(data: NodeJsonExporterData) {
		const flags = data['flags'];
		if (flags) {
			const bypass = flags['bypass'];
			if (bypass != null) {
				this._node.flags?.bypass?.set(bypass);
			}
			const display = flags['display'];
			if (display != null) {
				this._node.flags?.display?.set(display);
			}
			const optimize = flags['optimize'];
			if (optimize != null) {
				this._node.flags?.optimize?.set(optimize);
			}
		}
	}

	set_connection_points(connection_points_data: IoConnectionPointsData | undefined) {
		if (!connection_points_data) {
			return;
		}
		if (connection_points_data['in']) {
			this._node.io.saved_connection_points_data.set_in(connection_points_data['in']);
		}
		if (connection_points_data['out']) {
			this._node.io.saved_connection_points_data.set_out(connection_points_data['out']);
		}

		if (this._node.io.has_connection_points_controller) {
			this._node.io.connection_points.update_signature_if_required();
		}
	}

	setInputs(inputs_data?: InputData[]) {
		if (!inputs_data) {
			return;
		}

		let input_data: InputData;
		for (let i = 0; i < inputs_data.length; i++) {
			input_data = inputs_data[i];
			if (input_data && this._node.parent()) {
				if (CoreType.isString(input_data)) {
					const input_node_name = input_data;
					const input_node = this._node.nodeSibbling(input_node_name);
					this._node.setInput(i, input_node);
				} else {
					const input_node = this._node.nodeSibbling(input_data['node']);
					const input_index = input_data['index'];
					this._node.setInput(input_index, input_node, input_data['output']);
				}
			}
		}
	}

	process_nodes_ui_data(scene_importer: SceneJsonImporter, data: PolyDictionary<NodeJsonExporterUIData>) {
		if (!data) {
			return;
		}
		if (Poly.playerMode()) {
			return;
		}

		const node_names = Object.keys(data);
		for (let node_name of node_names) {
			const node = this._node.node(node_name);
			if (node) {
				const node_data = data[node_name];
				JsonImportDispatcher.dispatch_node(node).process_ui_data(scene_importer, node_data);
				// node.visit(JsonImporterVisitor).process_ui_data(node_data);
			}
		}
	}

	//
	//
	// PARAMS
	//
	//
	set_params(data?: PolyDictionary<ParamJsonExporterData<ParamType>>) {
		if (!data) {
			return;
		}
		const param_names = Object.keys(data);

		const params_update_options: ParamsUpdateOptions = {};
		for (let param_name of param_names) {
			const param_data = data[param_name] as ComplexParamJsonExporterData<ParamType>;
			const options = param_data['options'];
			// const is_spare = options && options['spare'] === true;

			// make camelCase if required
			if (false && param_name.includes('_')) {
				param_name = CoreString.camelCase(param_name);
			}

			const param_type = param_data['type']!;
			const has_param = this._node.params.has_param(param_name);
			let has_param_and_same_type = false;
			let param;
			if (has_param) {
				param = this._node.params.get(param_name);
				// we can safely consider same type if param_type is not mentioned
				if ((param && param.type() == param_type) || param_type == null) {
					has_param_and_same_type = true;
				}
			}

			if (has_param_and_same_type) {
				if (this._is_param_data_complex(param_data)) {
					this._process_param_data_complex(param_name, param_data);
				} else {
					this._process_param_data_simple(param_name, param_data as SimpleParamJsonExporterData<ParamType>);
				}
			} else {
				// it the param is a spare one,
				// we check if it is currently exists with same type first.
				// - if it is, we only update the value
				// - if it's not, we delete it and add it again
				params_update_options.names_to_delete = params_update_options.names_to_delete || [];
				params_update_options.names_to_delete.push(param_name);
				params_update_options.to_add = params_update_options.to_add || [];
				params_update_options.to_add.push({
					name: param_name,
					type: param_type,
					init_value: param_data['default_value'] as any,
					raw_input: param_data['raw_input'] as any,
					options: options,
				});

				// if (options && param_type) {
				// 	if (param_data['default_value']) {
				// 		if (has_param) {
				// 			this._node.params.delete_param(param_name);
				// 		}
				// 		param = this._node.add_param(param_type, param_name, param_data['default_value'], options);
				// 		if (param) {
				// 			JsonImportDispatcher.dispatch_param(param).process_data(param_data);
				// 		}
				// 	}
				// }
			}
		}

		// delete and create the spare params we need to
		const params_delete_required =
			params_update_options.names_to_delete && params_update_options.names_to_delete.length > 0;
		const params_add_required = params_update_options.to_add && params_update_options.to_add.length > 0;

		if (params_delete_required || params_add_required) {
			this._node.params.update_params(params_update_options);
			// update them based on the imported data
			for (let spare_param of this._node.params.spare) {
				const param_data = data[spare_param.name()] as ComplexParamJsonExporterData<ParamType>;
				// JsonImportDispatcher.dispatch_param(spare_param).process_data(param_data);
				if (!spare_param.parent_param && param_data) {
					if (this._is_param_data_complex(param_data)) {
						this._process_param_data_complex(spare_param.name(), param_data);
					} else {
						this._process_param_data_simple(
							spare_param.name(),
							param_data as SimpleParamJsonExporterData<ParamType>
						);
					}
				}
			}
		}
		// those hooks are useful for some gl nodes,
		// such as the constant, which needs to update its connections
		// based on another parameter, which will be set just before
		this._node.params.run_on_scene_load_hooks();
	}

	private _process_param_data_simple(param_name: string, param_data: SimpleParamJsonExporterData<ParamType>) {
		this._node.params.get(param_name)?.set(param_data);
	}

	private _process_param_data_complex(param_name: string, param_data: ComplexParamJsonExporterData<ParamType>) {
		const param = this._node.params.get(param_name);
		if (param) {
			JsonImportDispatcher.dispatch_param(param).process_data(param_data);
		}
		// return
		// const has_param = this._node.params.has_param(param_name);
		// const param_type = param_data['type']!;

		// let has_param_and_same_type = false;
		// let param;
		// if (has_param) {
		// 	param = this._node.params.get(param_name);
		// 	// we can safely consider same type if param_type is not mentioned
		// 	if ((param && param.type == param_type) || param_type == null) {
		// 		has_param_and_same_type = true;
		// 	}
		// }
		// if (has_param_and_same_type) {
		// 	param = this._node.params.get(param_name);
		// 	if (param) {
		// 		JsonImportDispatcher.dispatch_param(param).process_data(param_data);
		// 		// param.visit(JsonImporterVisitor).process_data(param_data);
		// 	}
		// } else {
		// 	const options = param_data['options'];
		// 	if (options && param_type) {
		// 		const is_spare = options['spare'] === true;
		// 		if (is_spare && param_data['default_value']) {
		// 			if (has_param) {
		// 				this._node.params.delete_param(param_name);
		// 			}
		// 			param = this._node.add_param(param_type, param_name, param_data['default_value'], options);
		// 			if (param) {
		// 				JsonImportDispatcher.dispatch_param(param).process_data(param_data);
		// 			}
		// 		}
		// 	}
		// }
	}

	private _is_param_data_complex(param_data: ParamJsonExporterData<ParamType>): boolean {
		// we can test here most param value serialized, except for ramp
		if (
			CoreType.isString(param_data) ||
			CoreType.isNumber(param_data) ||
			CoreType.isArray(param_data) ||
			CoreType.isBoolean(param_data)
		) {
			return false;
		}

		if (ObjectUtils.isObject(param_data)) {
			const keys = Object.keys(param_data);
			for (let complex_key of COMPLEX_PARAM_DATA_KEYS) {
				if (keys.includes(complex_key)) {
					return true;
				}
			}
		}

		return false;
	}

	set_persisted_config(persisted_config_data: object) {
		if (this._node.persisted_config) {
			this._node.persisted_config.load(persisted_config_data);
		}
	}

	from_data_custom(data: NodeJsonExporterData) {}
}
