import {PolyScene} from '../scene/PolyScene';
import {CoreGraphNode} from '../../core/graph/CoreGraphNode';
import {UIData} from './utils/UIData';
import {FlagsController, FlagsControllerD} from './utils/FlagsController';
import {NodeStatesController} from './utils/StatesController';
import {HierarchyParentController} from './utils/hierarchy/ParentController';
import {HierarchyChildrenController, NodeCreateOptions} from './utils/hierarchy/ChildrenController';
import {NodeLifeCycleController} from './utils/LifeCycleController';
import {TypedContainerController} from './utils/ContainerController';
import {NodeCookController, OnCookCompleteHook} from './utils/CookController';
import {NameController} from './utils/NameController';
import {CoreNodeSerializer, NodeSerializerData} from './utils/CoreNodeSerializer';
import {ParamsController} from './utils/params/ParamsController';
import {ParamConstructorMap} from '../params/types/ParamConstructorMap';
import {ParamInitValuesTypeMap} from '../params/types/ParamInitValuesTypeMap';
import {NodeParamsConfig} from './utils/params/ParamsConfig';
import {ParamsValueAccessor, ParamsValueAccessorType} from './utils/params/ParamsValueAccessor';
// import {ProcessingContext} from './utils/ProcessingContext';
import {IOController} from './utils/io/IOController';
import {NodeEvent} from '../poly/NodeEvent';
import {BaseNodeByContextMap, NodeContext} from '../poly/NodeContext';
import {ParamsAccessorType, ParamsAccessor} from './utils/params/ParamsAccessor';
// import {ContainerMap, ContainerType} from '../containers/utils/ContainerMap';
import {ContainableMap} from '../containers/utils/ContainableMap';
import {ParamOptions} from '../params/utils/OptionsController';
import {ParamType} from '../poly/ParamType';
import {DisplayNodeController} from './utils/DisplayNodeController';
// import {NodeTypeMap} from '../containers/utils/ContainerMap';
import {ParamInitValueSerialized} from '../params/types/ParamInitValueSerialized';
import {ModuleName} from '../poly/registers/modules/Common';
import {BasePersistedConfig} from './utils/BasePersistedConfig';
import {AssemblerName} from '../poly/registers/assemblers/_BaseRegister';
import {PolyNodeController} from './utils/poly/PolyNodeController';
import {CoreGraphNodeId} from '../../core/graph/CoreGraph';
import {PolyDictionary} from '../../types/GlobalTypes';
import {SetInputsOptions} from './utils/io/InputsController';
import {OnNodeRegisterCallback} from '../poly/registers/nodes/NodesRegister';
import {EventDispatcher, BaseEvent, EventListener} from 'three';
import {CoreObjectType, ObjectContent} from '../../core/geometry/ObjectContent';
export interface NodeDeletedEmitData {
	parent_id: CoreGraphNodeId;
}
type AnyString= 'any-string'
const ANY_STRING:AnyString='any-string'
export interface NodeCreatedEmitData {
	child_node_json: NodeSerializerData;
}
type EmitDataByNodeEventMapGeneric = {[key in NodeEvent]: any};
export interface EmitDataByNodeEventMap extends EmitDataByNodeEventMapGeneric {
	[NodeEvent.CREATED]: NodeCreatedEmitData;
	[NodeEvent.DELETED]: NodeDeletedEmitData;
	[NodeEvent.ERROR_UPDATED]: undefined;
}
export interface IntegrationData {
	name: string;
	data: PolyDictionary<string>;
}

export interface BaseNodeEvent extends BaseEvent {
	target?: BaseNodeType;
	// [attachment: string]: any;
}
export type NodeEventListener = EventListener<BaseNodeEvent, string, EventDispatcher<BaseNodeEvent>>;
export const DEFAULT_DATA_TYPE = 'default';

/**
 *
 *
 * TypedNode is the base class that all nodes inherit from. This inherits from [CoreGraphNode](/docs/api/CoreGraphNode).
 *
 */
export class TypedNode<NC extends NodeContext, K extends NodeParamsConfig> extends CoreGraphNode {
	containerController: TypedContainerController<NC> = new TypedContainerController<NC>(this);

	private _parentController: HierarchyParentController | undefined;

	private _uiData: UIData | undefined;

	private _states: NodeStatesController<NC> | undefined;
	private _lifecycle: NodeLifeCycleController | undefined;
	private _serializer: CoreNodeSerializer | undefined;
	private _cookController: NodeCookController<NC> | undefined;
	public readonly flags: FlagsController | undefined;
	public readonly displayNodeController: DisplayNodeController | undefined;
	public readonly persisted_config: BasePersistedConfig | undefined;

	private _paramsController: ParamsController | undefined;
	readonly paramsConfig: K | undefined;
	readonly pv: ParamsValueAccessorType<K> = (<unknown>new ParamsValueAccessor<K>()) as ParamsValueAccessorType<K>;
	// readonly pv: ParamsValueAccessor<K> = new ParamsValueAccessor<K>(this);
	readonly p: ParamsAccessorType<K> = (<unknown>new ParamsAccessor<K>()) as ParamsAccessorType<K>;
	copy_param_values(node: TypedNode<NC, K>) {
		const non_spare = this.params.non_spare;
		for (const param of non_spare) {
			const other_param = node.params.get(param.name());
			if (other_param) {
				param.copyValue(other_param);
			}
		}
	}
	dataType(): string {
		return DEFAULT_DATA_TYPE;
	}

	private _nameController: NameController | undefined;
	get parentController(): HierarchyParentController {
		return (this._parentController = this._parentController || new HierarchyParentController(this));
	}
	static displayedInputNames(): string[] | undefined {
		return undefined;
	}
	displayedInputNames(): string[] | undefined {
		return (this.constructor as any as TypedNode<NC, K>).displayedInputNames();
	}

	private _childrenController: HierarchyChildrenController | undefined;
	protected _childrenControllerContext: NodeContext | undefined;
	childrenControllerContext() {
		return this._childrenControllerContext;
	}
	private _create_childrenController(): HierarchyChildrenController | undefined {
		if (this._childrenControllerContext) {
			return new HierarchyChildrenController(this, this._childrenControllerContext);
		}
	}
	get childrenController(): HierarchyChildrenController | undefined {
		return (this._childrenController = this._childrenController || this._create_childrenController());
	}
	childrenAllowed(): boolean {
		return this._childrenControllerContext != null;
	}
	sceneReadonly(): boolean {
		return false;
	}

	get uiData(): UIData {
		return (this._uiData = this._uiData || new UIData(this));
	}

	get states(): NodeStatesController<NC> {
		return (this._states = this._states || new NodeStatesController(this));
	}
	get lifecycle(): NodeLifeCycleController {
		return (this._lifecycle = this._lifecycle || new NodeLifeCycleController(this));
	}

	get serializer(): CoreNodeSerializer | undefined {
		return this._serializer;
	}

	get cookController(): NodeCookController<NC> {
		return (this._cookController = this._cookController || new NodeCookController(this));
	}
	protected _io: IOController<NC> | undefined;
	get io(): IOController<NC> {
		return (this._io = this._io || new IOController(this));
	}
	get nameController(): NameController {
		return (this._nameController = this._nameController || new NameController(this));
	}
	/**
	 * sets the name of a node. Note that if a sibling node already has that name, it will be updated to be unique.
	 *
	 */
	override setName(name: string) {
		this.nameController.setName(name);
	}
	_setCoreName(name: string) {
		this._name = name;
	}

	get params(): ParamsController {
		return (this._paramsController = this._paramsController || new ParamsController(this));
	}
	// get processing_context(): ProcessingContext {
	// 	return (this._processing_context = this._processing_context || new ProcessingContext(this));
	// }

	constructor(scene: PolyScene, nodeName: string = 'BaseNode', public createOptions?: NodeCreateOptions) {
		super(scene, nodeName);
		const serializerClass = createOptions?.serializerClass;
		if (serializerClass) {
			this._serializer = new serializerClass(this);
		}
	}

	private _initialized: boolean = false;
	public initializeBaseAndNode() {
		if (!this._initialized) {
			this._initialized = true;

			this.displayNodeController?.initializeNode();

			this.initializeBaseNode(); // for base classes of Sop, Obj...
			this.initializeNode(); // for Derivated node clases, like BoxSop, TransformSop...
			if (this.polyNodeController) {
				this.polyNodeController.initializeNode();
			}
		} else {
			console.warn('node already initialized');
		}
	}
	protected initializeBaseNode() {}
	protected initializeNode() {}

	static type(): string {
		throw 'type to be overriden';
	}
	static onRegister: OnNodeRegisterCallback | undefined;
	/**
	 * returns the type of the node.
	 *
	 */
	type() {
		const c = this.constructor as typeof BaseNodeClass;
		return c.type();
	}
	static context(): NodeContext {
		console.error('node has no node_context', this);
		throw 'context requires override';
	}
	/**
	 * returns the context.
	 *
	 */
	context(): NodeContext {
		const c = this.constructor as typeof BaseNodeClass;
		return c.context();
	}

	static requireWebGL2(): boolean {
		return false;
	}
	requireWebGL2(): boolean {
		const c = this.constructor as typeof BaseNodeClass;
		return c.requireWebGL2();
	}

	setParent(parent: BaseNodeType | null) {
		this.parentController.setParent(parent);
	}
	/**
	 * returns the parent.
	 *
	 */
	parent() {
		return this.parentController.parent();
	}
	insideALockedParent(): boolean {
		return this.lockedParent() != null;
	}
	lockedOrInsideALockedParent(): boolean {
		return this.polyNodeController?.locked() || this.insideALockedParent();
	}
	selfOrLockedParent(): BaseNodeType | null {
		if (this.polyNodeController?.locked()) {
			return this;
		}
		return this.lockedParent();
	}
	lockedParent(): BaseNodeType | null {
		const parent = this.parent();
		if (!parent) {
			return null;
		}
		if (parent.polyNodeController && parent.polyNodeController.locked()) {
			return parent;
		}
		return parent.lockedParent();
	}
	firstAncestorWithContext<N extends NodeContext>(context: N) {
		return this.parentController.firstAncestorWithContext(context);
	}
	root() {
		return this._scene.root();
	}
	/**
	 * returns the path.
	 *
	 */
	path(relative_to_parent?: BaseNodeType): string {
		return this.parentController.path(relative_to_parent);
	}

	// params
	createParams() {}
	addParam<T extends ParamType>(
		type: T,
		name: string,
		default_value: ParamInitValuesTypeMap[T],
		options?: ParamOptions
	): ParamConstructorMap[T] | undefined {
		return this._paramsController?.addParam(type, name, default_value, options);
	}
	paramDefaultValue(name: string): ParamInitValueSerialized {
		return null;
	}

	// cook
	cook(inputContents: any[]): any {
		return null;
	}
	/**
	 * registers a callback that will be run every time the node finishes cooking.
	 *
	 */
	onCookEnd(callbackName: string, callback: OnCookCompleteHook) {
		this.cookController.registerOnCookEnd(callbackName, callback);
	}

	/**
	 * returns a promise that will be resolved when the node finishes cooking.
	 *
	 */
	async compute() {
		if (this.isDirty() || this.flags?.bypass?.active()) {
			return await this.containerController.compute();
		} else {
			return this.containerController.container();
		}
	}
	_setContainer(content: ContainableMap[NC] /*, message: string | null = null*/) {
		// TODO: typescript: why is this a type of never
		this.containerController.container().set_content(content as never); //, this.self.cook_eval_key());
		// if (content != null) {
		// 	// if (content.name==null) {
		// 	// 	content.name = this.path();
		// 	// }
		// 	// if (!(content as any).node) {
		// 	// 	(content as any).node = this;
		// 	// }
		// }
		this.cookController.endCook(/*message*/);
	}

	/**
	 * create a node.
	 *
	 */
	createNode(nodeClass: any, options?: NodeCreateOptions) {
		return this.childrenController?.createNode(nodeClass, options);
	}
	createOperationContainer(type: string, operation_container_name: string, options?: NodeCreateOptions) {
		return this.childrenController?.createOperationContainer(type, operation_container_name, options);
	}
	/**
	 * removes a child node
	 *
	 */
	removeNode(node: BaseNodeType) {
		this.childrenController?.removeNode(node);
	}
	override dispose() {
		super.dispose();
		this.setParent(null);
		if (this._nameController) {
			this._nameController.dispose();
			this._nameController = undefined;
		}
		if (this._io) {
			this._io.dispose();
			this._io = undefined;
		}
		if (this._lifecycle) {
			this._lifecycle.dispose();
			this._lifecycle = undefined;
		}
		if (this.displayNodeController) {
			this.displayNodeController.dispose();
			// this.displayNodeController = undefined
		}
		if (this._childrenController) {
			this._childrenController.dispose();
			this._childrenController = undefined;
		}
		if (this._paramsController) {
			this._paramsController.dispose();
			this._paramsController = undefined;
		}
		if (this._cookController) {
			this._cookController.dispose();
			this._cookController = undefined;
		}
		if (this._serializer) {
			this._serializer.dispose();
			this._serializer = undefined;
		}
		if (this._uiData) {
			this._uiData.dispose();
			this._uiData = undefined;
		}
	}

	/**
	 * returns the list of children
	 *
	 */
	children(): Readonly<BaseNodeType[]> {
		return this.childrenController?.children() || [];
	}
	/**
	 * returns a child node
	 *
	 */
	node(path: string) {
		return this.parentController?.findNode(path) || null;
	}
	/**
	 * returns a sibling node
	 *
	 */
	nodeSibling(name: string): BaseNodeByContextMap[NC] | null {
		const parent = this.parent();
		if (parent) {
			const node = parent.childrenController?.childByName(name);
			if (node) {
				return node as BaseNodeByContextMap[NC];
			}
		}
		return null;
	}
	/**
	 * returns the children matching the type
	 *
	 */
	nodesByType(type: string) {
		return this.childrenController?.nodesByType(type) || [];
	}

	/**
	 * sets a node as input
	 *
	 */
	setInput(
		inputIndexOrName: number | string,
		node: BaseNodeByContextMap[NC] | null,
		outputIndexOrName?: number | string,
		options?: SetInputsOptions
	) {
		this.io.inputs.setInput(inputIndexOrName, node, outputIndexOrName, options);
	}

	// emit
	emit(event_name: NodeEvent.CREATED, data: EmitDataByNodeEventMap[NodeEvent.CREATED]): void;
	emit(event_name: NodeEvent.DELETED, data: EmitDataByNodeEventMap[NodeEvent.DELETED]): void;
	emit(event_name: NodeEvent.NAME_UPDATED): void;
	emit(event_name: NodeEvent.OVERRIDE_CLONABLE_STATE_UPDATE): void;
	emit(event_name: NodeEvent.NAMED_INPUTS_UPDATED): void;
	emit(event_name: NodeEvent.NAMED_OUTPUTS_UPDATED): void;
	emit(event_name: NodeEvent.INPUTS_UPDATED): void;
	emit(event_name: NodeEvent.PARAMS_UPDATED): void;
	emit(event_name: NodeEvent.UI_DATA_POSITION_UPDATED): void;
	emit(event_name: NodeEvent.UI_DATA_COMMENT_UPDATED): void;
	emit(event_name: NodeEvent.ERROR_UPDATED): void;
	emit(event_name: NodeEvent.FLAG_BYPASS_UPDATED): void;
	emit(event_name: NodeEvent.FLAG_DISPLAY_UPDATED): void;
	emit(event_name: NodeEvent.FLAG_OPTIMIZE_UPDATED): void;
	emit(event_name: NodeEvent.SELECTION_UPDATED): void;
	emit(event_name: NodeEvent.POLY_NODE_LOCK_STATE_UPDATED): void;
	emit(event_name: NodeEvent, data: object | null = null): void {
		this.scene().dispatchController.dispatch(this, event_name, data);
	}
	private __eventsDispatcher: EventDispatcher<{[ANY_STRING]:any}> | undefined;
	private _eventsDispatcher() {
		return (this.__eventsDispatcher = this.__eventsDispatcher || new EventDispatcher());
	}
	dispatchEvent(event: BaseEvent<string>) {
		this._eventsDispatcher().dispatchEvent(event);
	}
	addEventListener(type: string, listener: NodeEventListener) {
		this._eventsDispatcher().addEventListener(type as AnyString, listener);
	}
	removeEventListener(type: string, listener: NodeEventListener) {
		this._eventsDispatcher().removeEventListener(type as AnyString, listener);
	}

	// serializer
	toJSON(includeParamComponents: boolean = false) {
		if (!this._serializer) {
			return;
		}
		return this._serializer.toJSON(includeParamComponents);
	}

	// modules
	public requiredModules(): ModuleName[] | void {}
	public usedAssembler(): AssemblerName | void {}
	public integrationData(): IntegrationData | void {}

	// poly nodes
	public readonly polyNodeController: PolyNodeController | undefined;

	// error processing
	processError(e: unknown) {
		return e;
	}

	public updateObjectOnAdd(object: ObjectContent<CoreObjectType>, parent: ObjectContent<CoreObjectType>) {}
	public updateObjectOnRemove(object: ObjectContent<CoreObjectType>, parent: ObjectContent<CoreObjectType>) {}
}

export type BaseNodeType = TypedNode<any, any>;
export class BaseNodeClass extends TypedNode<any, any> {}

export class BaseNodeClassWithDisplayFlag extends TypedNode<any, any> {
	public override readonly flags: FlagsControllerD = new FlagsControllerD(this);
}
