// import {TypedParamVisitor} from './_Base';
import {BaseParamType} from './_Base';
import {TypedPathParam} from './_BasePath';
import {CoreWalker} from '../../core/Walker';
import {BaseNodeType} from '../nodes/_Base';
import {ParamType} from '../poly/ParamType';
import {ParamValuesTypeMap} from './types/ParamValuesTypeMap';
import {ParamEvent} from '../poly/ParamEvent';
import {ParamInitValuesTypeMap} from './types/ParamInitValuesTypeMap';
import {NodeContext, BaseNodeByContextMap, ChildrenNodeMapByContextMap} from '../poly/NodeContext';
import {ParamConstructorMap} from './types/ParamConstructorMap';
import {CoreType} from '../../core/Type';

enum OperatorPathMode {
	NODE = 'NODE',
	PARAM = 'PARAM',
}

export class OperatorPathParam extends TypedPathParam<ParamType.OPERATOR_PATH> {
	private _found_node: BaseNodeType | null = null;
	private _found_node_with_expected_type: BaseNodeType | null = null;
	private _found_param: BaseParamType | null = null;
	private _found_param_with_expected_type: BaseParamType | null = null;

	static type() {
		return ParamType.OPERATOR_PATH;
	}
	get default_value_serialized() {
		return this.default_value;
	}
	get raw_input_serialized() {
		return `${this._raw_input}`;
	}
	get value_serialized() {
		return `${this.value}`;
	}
	protected _copy_value(param: OperatorPathParam) {
		this.set(param.value_serialized);
	}
	static are_raw_input_equal(
		raw_input1: ParamInitValuesTypeMap[ParamType.OPERATOR_PATH],
		raw_input2: ParamInitValuesTypeMap[ParamType.OPERATOR_PATH]
	) {
		return raw_input1 == raw_input2;
	}
	static are_values_equal(
		val1: ParamValuesTypeMap[ParamType.OPERATOR_PATH],
		val2: ParamValuesTypeMap[ParamType.OPERATOR_PATH]
	) {
		return val1 == val2;
	}
	get is_default(): boolean {
		return this._value == this.default_value;
	}
	setNode(node: BaseNodeType) {
		this.set(node.fullPath());
	}
	protected process_raw_input() {
		if (this._value != this._raw_input) {
			this._value = this._raw_input;
			this.setDirty();
			this.emitController.emit(ParamEvent.VALUE_UPDATED);
		}
	}
	protected async process_computation() {
		this.find_target();
	}
	find_target() {
		if (!this.node) {
			return;
		}
		const path = this._value;
		let node: BaseNodeType | null = null;
		let param: BaseParamType | null = null;
		const path_non_empty = path != null && path !== '';
		const mode: OperatorPathMode = this.options.param_selection_options()
			? OperatorPathMode.PARAM
			: OperatorPathMode.NODE;

		this.scene().referencesController.reset_reference_from_param(this); // must be before decomposed path is changed
		this.decomposed_path.reset();
		if (path_non_empty) {
			if (mode == OperatorPathMode.PARAM) {
				param = CoreWalker.find_param(this.node, path, this.decomposed_path);
			} else {
				node = CoreWalker.find_node(this.node, path, this.decomposed_path);
			}
		}

		const current_found_entity = mode == OperatorPathMode.PARAM ? this._found_param : this._found_node;
		const newly_found_entity = mode == OperatorPathMode.PARAM ? param : node;

		this.scene().referencesController.set_named_nodes_from_param(this);
		if (node) {
			this.scene().referencesController.set_reference_from_param(this, node);
		}

		if (current_found_entity?.graphNodeId() !== newly_found_entity?.graphNodeId()) {
			const dependent_on_found_node = this.options.dependent_on_found_node();

			if (this._found_node) {
				if (dependent_on_found_node) {
					this.removeGraphInput(this._found_node);
				} else {
					// this._found_node.remove_param_referree(this) // TODO: typescript
				}
			}
			if (mode == OperatorPathMode.PARAM) {
				this._found_param = param;
				this._found_node = null;
			} else {
				this._found_node = node;
				this._found_param = null;
			}

			if (node) {
				this._assign_found_node(node);
			}
			if (param) {
				this._assign_found_param(param);
			}

			this.options.execute_callback();
		}
		this.removeDirtyState();
	}

	private _assign_found_node(node: BaseNodeType) {
		const dependent_on_found_node = this.options.dependent_on_found_node();
		if (this._is_node_expected_context(node)) {
			if (this._is_node_expected_type(node)) {
				this._found_node_with_expected_type = node;
				if (dependent_on_found_node) {
					this.addGraphInput(node);
				}
			} else {
				this.states.error.set(
					`node type is ${node.type()} but the params expects one of ${(
						this._expected_node_types() || []
					).join(', ')}`
				);
			}
		} else {
			this.states.error.set(
				`node context is ${node.nodeContext()} but the params expects a ${this._expected_context()}`
			);
		}
	}
	private _assign_found_param(param: BaseParamType) {
		if (this._is_param_expected_type(param)) {
			this._found_param_with_expected_type = param;
		} else {
			this.states.error.set(
				`param type is ${param.type()} but the params expects a ${this._expected_param_type()}`
			);
		}
	}

	found_node() {
		return this._found_node;
	}
	found_param() {
		return this._found_param;
	}
	found_node_with_context<N extends NodeContext>(context: N): BaseNodeByContextMap[N] | undefined {
		return this._found_node_with_expected_type as BaseNodeByContextMap[N];
		// if (node) {
		// 	if (node.node_context() == context) {
		// 		return node as BaseNodeByContextMap[N];
		// 	} else {
		// 		this.states.error.set(`expected node context to be ${context}, but was instead ${node.node_context()}`);
		// 	}
		// } else {
		// 	this.states.error.set('no node found');
		// }
	}
	// found_node_with_context_and_type<N extends NodeContext, K extends keyof ChildrenNodeMapByContextMap[N]>(
	// 	context: N,
	// 	type: K
	// ): ChildrenNodeMapByContextMap[N][K] | undefined {
	found_node_with_context_and_type<N extends NodeContext, K extends keyof ChildrenNodeMapByContextMap[N]>(
		context: N,
		type_or_types: K | K[]
	): ChildrenNodeMapByContextMap[N][K] | undefined {
		const node = this.found_node_with_context(context);
		if (node) {
			if (CoreType.isArray(type_or_types)) {
				for (let type of type_or_types) {
					if (node.type() == type) {
						return (<unknown>node) as ChildrenNodeMapByContextMap[N][K];
					}
				}
				this.states.error.set(
					`expected node type to be ${type_or_types.join(', ')}, but was instead ${node.type()}`
				);
			} else {
				const type = type_or_types;
				if (node.type() == type) {
					return (<unknown>node) as ChildrenNodeMapByContextMap[N][K];
				} else {
					this.states.error.set(`expected node type to be ${type}, but was instead ${node.type()}`);
				}
			}
		}
	}
	found_param_with_type<T extends ParamType>(type: T): ParamConstructorMap[T] | undefined {
		if (this._found_param_with_expected_type) {
			return this._found_param_with_expected_type as ParamConstructorMap[T];
		}
	}

	found_node_with_expected_type() {
		return this._found_node_with_expected_type;
	}
	private _expected_context() {
		return this.options.node_selection_context();
	}
	private _is_node_expected_context(node: BaseNodeType) {
		const expected_context = this._expected_context();
		if (expected_context == null) {
			return true;
		}
		const node_context = node.parent()?.childrenController?.context;
		return expected_context == node_context;
	}
	private _expected_node_types() {
		return this.options.node_selection_types();
	}
	private _expected_param_type() {
		return this.options.param_selection_type();
	}
	private _is_node_expected_type(node: BaseNodeType) {
		const expected_types = this._expected_node_types();
		if (expected_types == null) {
			return true;
		}
		return expected_types?.includes(node.type());
	}
	private _is_param_expected_type(param: BaseParamType) {
		const expected_types = this._expected_node_types();
		if (expected_types == null) {
			return true;
		}
		return expected_types.includes(param.type());
	}

	notify_path_rebuild_required(node: BaseNodeType) {
		this.decomposed_path.update_from_name_change(node);
		const new_path = this.decomposed_path.to_path();
		this.set(new_path);
	}
	notify_target_param_owner_params_updated(node: BaseNodeType) {
		this.setDirty();
	}
}
