import {DisplayNodeControllerCallbacks, DisplayNodeController} from '../../../utils/DisplayNodeController';
import {SubnetOutputSopNode} from '../../SubnetOutput';
import {CoreGraphNode} from '../../../../../core/graph/CoreGraphNode';
import {TypedSopNode, BaseSopNodeType} from '../../_Base';
import {NodeContext} from '../../../../poly/NodeContext';
import {GeoNodeChildrenMap} from '../../../../poly/registers/nodes/Sop';

import {NodeParamsConfig} from '../../../utils/params/ParamsConfig';
import {CoreGroup} from '../../../../../core/geometry/Group';
import {ParamsInitData} from '../../../utils/io/IOController';
import {Constructor, valueof} from '../../../../../types/GlobalTypes';

export class SubnetSopNodeLike<T extends NodeParamsConfig> extends TypedSopNode<T> {
	initializeBaseNode() {
		super.initializeBaseNode();
		this.children_display_controller.initializeNode();
		// the inputs will be evaluated by the child input nodes
		this.cookController.disallow_inputs_evaluation();
	}

	// display_node and children_display controllers
	public readonly children_display_controller: SopSubnetChildrenDisplayController = new SopSubnetChildrenDisplayController(
		this
	);
	public readonly display_node_controller: DisplayNodeController = new DisplayNodeController(
		this,
		this.children_display_controller.display_node_controller_callbacks()
	);
	//

	protected _children_controller_context = NodeContext.SOP;

	createNode<S extends keyof GeoNodeChildrenMap>(
		node_class: S,
		params_init_value_overrides?: ParamsInitData
	): GeoNodeChildrenMap[S];
	createNode<K extends valueof<GeoNodeChildrenMap>>(
		node_class: Constructor<K>,
		params_init_value_overrides?: ParamsInitData
	): K;
	createNode<K extends valueof<GeoNodeChildrenMap>>(
		node_class: Constructor<K>,
		params_init_value_overrides?: ParamsInitData
	): K {
		return super.createNode(node_class, params_init_value_overrides) as K;
	}
	children() {
		return super.children() as BaseSopNodeType[];
	}
	nodesByType<K extends keyof GeoNodeChildrenMap>(type: K): GeoNodeChildrenMap[K][] {
		return super.nodesByType(type) as GeoNodeChildrenMap[K][];
	}

	async cook(input_contents: CoreGroup[]) {
		const child_output_node = this.children_display_controller.output_node();
		if (child_output_node) {
			const container = await child_output_node.requestContainer();
			const core_content = container.coreContent();
			if (core_content) {
				this.setCoreGroup(core_content);
			} else {
				if (child_output_node.states.error.active()) {
					this.states.error.set(child_output_node.states.error.message());
				} else {
					this.setObjects([]);
				}
			}
		} else {
			this.states.error.set('no output node found inside subnet');
		}
	}
}

export class SopSubnetChildrenDisplayController {
	private _output_node_needs_update: boolean = true;
	private _output_node: SubnetOutputSopNode | undefined;
	private _graph_node: CoreGraphNode | undefined;
	constructor(private node: SubnetSopNodeLike<any>) {}

	dispose() {
		this._graph_node?.dispose();
	}

	display_node_controller_callbacks(): DisplayNodeControllerCallbacks {
		return {
			on_display_node_remove: () => {
				this.node.setDirty();
			},
			on_display_node_set: () => {
				this.node.setDirty();
			},
			on_display_node_update: () => {
				this.node.setDirty();
			},
		};
	}

	output_node() {
		if (this._output_node_needs_update) {
			this._update_output_node();
		}
		return this._output_node;
	}

	initializeNode() {
		const display_flag = this.node.flags?.display;
		if (display_flag) {
			display_flag.add_hook(() => {
				if (display_flag.active()) {
					this.node.setDirty();
				}
			});
		}

		this.node.lifecycle.add_on_child_add_hook(() => {
			this._output_node_needs_update = true;
			this.node.setDirty();
		});
		this.node.lifecycle.add_on_child_remove_hook(() => {
			this._output_node_needs_update = true;
			this.node.setDirty();
		});
	}

	private _update_output_node() {
		const found_node = this.node.nodesByType(SubnetOutputSopNode.type())[0];
		if (
			this._output_node == null ||
			found_node == null ||
			this._output_node.graphNodeId() != found_node.graphNodeId()
		) {
			if (this._graph_node && this._output_node) {
				this._graph_node.removeGraphInput(this._output_node);
			}

			this._output_node = found_node;

			if (this._output_node) {
				this._graph_node = this._graph_node || this._create_graph_node();

				this._graph_node.addGraphInput(this._output_node);
			}
		}
	}

	private _create_graph_node() {
		const graph_node = new CoreGraphNode(this.node.scene(), 'subnet_children_display_controller');
		graph_node.addPostDirtyHook('subnet_children_display_controller', () => {
			this.node.setDirty();
		});
		return graph_node;
	}
}
