import {BaseNodeClass, BaseNodeType} from './nodes/_Base';
import {PolyScene} from './scene/PolyScene';
import {RenderersController} from './poly/RenderersController';
import {PolyCanvasRegister} from './poly/PolyCanvasRegister';
import {PolyLibsController} from './poly/PolyLibsController';

import {NodeContext} from './poly/NodeContext';
import {BaseCoreLogger} from '../core/logger/Base';
import {BaseOperation} from './operations/_Base';
import {PolyDictionary} from '../types/GlobalTypes';
import {BlobsController} from './poly/BlobsController';
import {AssetUrlsController} from './poly/AssetUrlsController';
// import {SelfContainedScenesLoader} from './poly/SelfContainedSceneLoader';
import {PolyPerformanceformanceController} from './poly/PerformanceController';
import {ScenesRegister} from './poly/ScenesRegister';
// import {LogoController} from './poly/LogoController';
import {PolyThirdPartyController} from './poly/ThirdPartyController';
import {PolyOnObjectsAddRemoveHooksController} from './poly/PolyOnObjectsAddRemoveHooksController';
import {PolyOnSceneUpdatedHooksController} from './poly/PolyOnSceneUpdatedHooksController';
import {PolySpecializedChildrenController} from './poly/PolySpecializedChildrenController';
import {Camera} from 'three';

// registers
import {PluginsRegister, WrapConfigurePolygonjsCallback} from './poly/registers/plugins/PluginsRegister';
import {PolyCamerasRegister, ViewerCreateCallback} from './poly/registers/cameras/PolyCamerasRegister';
import {BaseModule} from './poly/registers/modules/_BaseModule';
import {ModuleName} from './poly/registers/modules/Common';
import {DynamicModulesRegister} from './poly/registers/modules/DynamicModulesRegister';
import {ExpressionRegister} from './poly/registers/expressions/ExpressionRegister';
import {AssemblersRegister} from './poly/registers/assemblers/AssemblersRegistry';
import {
	NodesRegister,
	NodeRegisterOptions,
	OperationRegisterOptions,
	BaseNodeConstructor,
	OperationsRegister,
} from './poly/registers/nodes/NodesRegister';
import {PolyPluginInterface} from './poly/registers/plugins/Plugin';
import {NamedFunctionRegister} from './poly/registers/functions/FunctionsRegister';
import {NamedFunctionRegisterOptions} from './poly/registers/functions/_BaseRegister';
import {ModuleRegisterOptions} from './poly/registers/modules/_BaseRegister';
import {BaseNamedFunction} from './functions/_Base';
//

declare global {
	var __POLYGONJS_POLY_INSTANCE__: PolyEngine;
}

// having __POLYGONJS_VERSION__ here really adds friction
// for compiling this after exporting from the editor
// UPDATE: it can be used now as the editor does not compile anymore
// and we would use that to check if the editor is the same version as the installed one
declare var __POLYGONJS_VERSION__: string;
const POLYGONJS_VERSION = __POLYGONJS_VERSION__;
export class PolyEngine {
	// static _instance: Poly | undefined;
	public readonly renderersController: RenderersController = new RenderersController(this);
	public readonly canvasRegister: PolyCanvasRegister = new PolyCanvasRegister(this);
	public readonly scenesRegister: ScenesRegister = new ScenesRegister();
	public readonly nodesRegister: NodesRegister = new NodesRegister(this);
	public readonly operationsRegister: OperationsRegister = new OperationsRegister(this);
	public readonly expressionsRegister: ExpressionRegister = new ExpressionRegister();
	public readonly assemblersRegister: AssemblersRegister = new AssemblersRegister();
	public readonly pluginsRegister: PluginsRegister = new PluginsRegister(this);
	public readonly camerasRegister: PolyCamerasRegister = new PolyCamerasRegister(this);
	public readonly modulesRegister: DynamicModulesRegister = new DynamicModulesRegister(this);
	public readonly namedFunctionsRegister: NamedFunctionRegister = new NamedFunctionRegister();
	public readonly blobs: BlobsController = new BlobsController(this);
	public readonly assetUrls: AssetUrlsController = new AssetUrlsController();
	// public readonly logo = new LogoController();
	public readonly thirdParty = new PolyThirdPartyController();
	public readonly onObjectsAddRemoveHooks = new PolyOnObjectsAddRemoveHooksController();
	public readonly onSceneUpdatedHooks = new PolyOnSceneUpdatedHooksController();
	public readonly specializedChildren = new PolySpecializedChildrenController();
	// public readonly selfContainedScenesLoader: SelfContainedScenesLoader = new SelfContainedScenesLoader();
	public readonly performance: PolyPerformanceformanceController = new PolyPerformanceformanceController();
	scenesByUuid: PolyDictionary<PolyScene> = {};
	_env: string | undefined;
	private _playerMode: boolean = true;
	private _logger: BaseCoreLogger | null = null;

	static _instance_() {
		// we are using a window globals to ensure 2 instances can never be created
		// even when the js are compiled by different means,
		// which can happen in the editor.
		if (globalThis.__POLYGONJS_POLY_INSTANCE__) {
			return globalThis.__POLYGONJS_POLY_INSTANCE__;
		} else {
			const instance = new PolyEngine();
			globalThis.__POLYGONJS_POLY_INSTANCE__ = instance;
			// this._instance = instance
			return globalThis.__POLYGONJS_POLY_INSTANCE__;
		}
		// return (this._instance = this._instance || new Poly());
	}
	private constructor() {}

	version() {
		return POLYGONJS_VERSION;
	}

	setPlayerMode(mode: boolean) {
		this._playerMode = mode;
	}
	playerMode() {
		return this._playerMode;
	}
	dispose() {
		this.scenesRegister.dispose();
		this.renderersController.dispose();
	}

	registerNode(node: BaseNodeConstructor, tab_menu_category?: string | string[], options?: NodeRegisterOptions) {
		this.nodesRegister.register(node, tab_menu_category, options);
	}
	registerOperation(operation: typeof BaseOperation, options?: OperationRegisterOptions) {
		this.operationsRegister.register(operation, options);
	}
	registerNamedFunction(namedFunction: typeof BaseNamedFunction, options?: NamedFunctionRegisterOptions) {
		this.namedFunctionsRegister.register(namedFunction, options);
	}

	registerCamera<C extends Camera>(cameraClass: any, viewerCreateCallback: ViewerCreateCallback<C>) {
		this.camerasRegister.register(cameraClass, viewerCreateCallback);
	}
	registerCameraNodeType(nodeType: string) {
		this.camerasRegister.registerNodeType(nodeType);
	}
	registerPlugin(plugin: PolyPluginInterface) {
		this.pluginsRegister.register(plugin);
	}
	wrapConfigurePolygonjs(callback: WrapConfigurePolygonjsCallback) {
		this.pluginsRegister.wrapConfigurePolygonjs(callback);
	}

	registeredNodes(parentNode: BaseNodeType): PolyDictionary<typeof BaseNodeClass> {
		return this.nodesRegister.registeredNodes(parentNode);
	}
	registeredOperation(parent_context: NodeContext, operation_type: string): typeof BaseOperation | undefined {
		return this.operationsRegister.registeredOperation(parent_context, operation_type);
	}
	// registeredCameraTypes() {
	// 	return this.camerasRegister.registeredTypes();
	// }
	registerModule(module: BaseModule<ModuleName>, options?: ModuleRegisterOptions) {
		this.modulesRegister.register(module.moduleName, module /*.module()*/, options);
	}

	inWorkerThread() {
		return false;
	}

	//
	//
	// LIBS
	//
	//
	private _libs_controller: PolyLibsController | undefined;
	get libs() {
		return (this._libs_controller = this._libs_controller || new PolyLibsController());
	}

	//
	//
	// ENV
	//
	//
	setEnv(env: string) {
		this._env = env;
	}
	env() {
		return this._env;
	}

	//
	//
	// LOGGER
	//
	//
	setLogger(logger: BaseCoreLogger | null) {
		this._logger = logger;
	}
	logger() {
		return this._logger;
	}

	log(message?: any, ...optionalParams: any[]) {
		this._logger?.log(...[message, ...optionalParams]);
	}
	warn(message?: any, ...optionalParams: any[]) {
		this._logger?.warn(...[message, ...optionalParams]);
	}
	error(message?: any, ...optionalParams: any[]) {
		this._logger?.error(...[message, ...optionalParams]);
	}
}

export const Poly = PolyEngine._instance_();
