import {
	type Node,
	createWrappedNode,
	IndentationText,
	Project,
	QuoteKind,
	ScriptTarget,
	SourceFile,
	TextChange,
} from 'ts-morph';
import ts, { type Declaration, factory, idText, Statement } from 'typescript';

import type { Driver } from '../Definitions/Global/Drivers.js';
import { UnitOfMeasure } from '../Definitions/Global/UOM.js';

import { types } from 'util';
import { Family } from '../Definitions/index.js';
import {
	CommandDefinition,
	DataTypeDefinition,
	DriverDefinition,
	NodeClassDefinition,
	ParameterDefinition,
} from '../Model/ClassDefinition.js';
import { EnumDefinitionMap } from '../Model/EnumDefinition.js';
import { CodeFactory } from './CodeFactory.js';

import { createMemberName } from './EnumFactory.js';

// #region Type aliases (1)

type GeneratedNodeClass<T extends Family> = {
	family: T;
	name: string;
	id: string;
	path: string;
	statements: any;
	sourceFile: SourceFile;
};

// #endregion Type aliases (1)

// #region Classes (1)

export class NodeClassFactory extends CodeFactory {
	// #region Properties (2)

	public static _basePath: string;
	public static project = new Project({
		//@ts-ignore
		compilerOptions: { target: ScriptTarget.ESNext, module: ts.ModuleKind.Node18 },
		manipulationSettings: {
			usePrefixAndSuffixTextForRename: true,
			quoteKind: QuoteKind.Single,
			useTrailingCommas: false,
			indentationText: IndentationText.Tab,
			newLineKind: ts.NewLineKind.CarriageReturnLineFeed,
			insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
		},
	});

	static instance: NodeClassFactory = new NodeClassFactory(ts.factory);

	// #endregion Properties (2)

	// #region Public Static Getters And Setters (2)

	public static get basePath() {
		return (this._basePath ??= './');
	}

	public static set basePath(value: string) {
		this._basePath = value;
	}

	// #endregion Public Static Getters And Setters (2)

	// #region Public Static Methods (4)

	public static buildNodeClasses<T extends Family>(map: {
		[x: string]: NodeClassDefinition<T>;
	}): GeneratedNodeClass<T>[] {
		return Object.values(map)
			.filter(p => !p.equivalentTo)
			.map(p => {
				try {
					return NodeClassFactory.instance.createNodeClass(p);
				} catch (e) {
					console.error(e);
				}
			});
	}

	public createNodeClass<T extends Family>(nodeClassDef: NodeClassDefinition<T>) {
		let path = `${NodeClassFactory.basePath}/${Family[nodeClassDef.family]}/Generated/${nodeClassDef.name}.ts`;
		let sf = ts.createSourceFile(path, '', ts.ScriptTarget.ES2024, true, ts.ScriptKind.TS);
		//@ts-expect-error

		sf.statements = [
			this.createImportDeclaration('../../../Definitions/Global/index.js', ['Family', ' UnitOfMeasure']),
			this.createImportDeclaration('../../../Model/NodeInfo.js', ['NodeInfo'], true),
			this.createImportDeclaration('../../../ISY.js', ['ISY']),
			this.createImportDeclaration('../../../ISYNode.js', ['ISYNode']),
			this.createImportDeclaration('../../ISYDeviceNode.js', ['ISYDeviceNode']),
			this.createImportDeclaration('../index.js', ['Base']),
			this.createImportDeclaration('../../../Definitions/Global/Drivers.js', ['Driver']),
			this.createImportDeclaration('type-fest', ['IntRange'], true),
			this.createImportDeclaration('../../../Model/DriverState,js', ['DriverState'], true),
			factory.createImportDeclaration(
				undefined,
				factory.createImportClause(
					false,
					undefined,
					factory.createNamedImports([
						factory.createImportSpecifier(false, undefined, factory.createIdentifier(Family[nodeClassDef.family])),
					])
				),
				factory.createStringLiteral('../../../Definitions/index.js'),
				undefined
			),
			this.createImportDeclaration('../../NodeFactory.js', ['NodeFactory']),
			factory.createVariableStatement(
				undefined,
				factory.createVariableDeclarationList(
					[
						factory.createVariableDeclaration(
							factory.createIdentifier('nodeDefId'),
							undefined,
							undefined,
							factory.createStringLiteral(nodeClassDef.id)
						),
					],
					ts.NodeFlags.Const
				)
			),
			factory.createTypeAliasDeclaration(
				undefined,
				factory.createIdentifier('Commands'),
				undefined,
				factory.createTypeReferenceNode(this.createQualifiedName(nodeClassDef.name, 'Commands', 'Type'))
			),

			factory.createTypeAliasDeclaration(
				undefined,
				factory.createIdentifier('Drivers'),
				undefined,
				factory.createTypeReferenceNode(this.createQualifiedName(nodeClassDef.name, 'Drivers', 'Type'), undefined)
			),

			factory.createClassDeclaration(
				[factory.createToken(ts.SyntaxKind.ExportKeyword)],
				factory.createIdentifier(`${nodeClassDef.name}`),
				undefined,
				[
					factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
						factory.createExpressionWithTypeArguments(factory.createIdentifier('Base'), [
							factory.createTypeReferenceNode(factory.createIdentifier('Drivers'), undefined),
							factory.createTypeReferenceNode(factory.createIdentifier('Commands'), undefined),
						]),
					]),
					factory.createHeritageClause(ts.SyntaxKind.ImplementsKeyword, [
						factory.createExpressionWithTypeArguments(
							factory.createPropertyAccessExpression(
								factory.createIdentifier(nodeClassDef.name),
								factory.createIdentifier('Interface')
							),
							undefined
						),
					]),
				],
				[
					factory.createPropertyDeclaration(
						[
							factory.createToken(ts.SyntaxKind.PublicKeyword),
							factory.createToken(ts.SyntaxKind.OverrideKeyword),
							factory.createToken(ts.SyntaxKind.ReadonlyKeyword),
						],
						factory.createIdentifier('commands'),
						undefined,
						undefined,
						factory.createObjectLiteralExpression(
							[
								...Object.values(nodeClassDef.commands).map(c =>
									factory.createPropertyAssignment(
										factory.createIdentifier(c.id),
										factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier(c.name))
									)
								),
							],
							true
						)
					),
					// factory.createPropertyDeclaration(
					//   [factory.createToken(ts.SyntaxKind.PublicKeyword)],
					//   factory.createIdentifier("drivers"),
					//   undefined,
					//   factory.createTypeReferenceNode(factory.createIdentifier("Drivers"), undefined),
					//   factory.createObjectLiteralExpression([], false)
					// ),
					factory.createPropertyDeclaration(
						[factory.createToken(ts.SyntaxKind.StaticKeyword), factory.createToken(ts.SyntaxKind.OverrideKeyword)],
						factory.createIdentifier('nodeDefId'),
						undefined,
						undefined,
						factory.createStringLiteral(nodeClassDef.id)
					),
					NodeClassFactory.instance.createPropertyDeclaration(
						'implements',
						factory.createArrayLiteralExpression(
							nodeClassDef.implementedBy
								? [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)].concat(
										!nodeClassDef.implements || nodeClassDef.implements.length == 0
											? []
											: nodeClassDef.implements?.map(p => factory.createStringLiteral(p))
									)
								: [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)]
						),
						NodeClassFactory.instance.createModifiers(ts.ModifierFlags.Static | ts.ModifierFlags.Override)
					),
					factory.createPropertyDeclaration(
						[factory.createToken(ts.SyntaxKind.DeclareKeyword), factory.createToken(ts.SyntaxKind.ReadonlyKeyword)],
						factory.createIdentifier('nodeDefId'),
						undefined,
						nodeClassDef.equivalents
							? NodeClassFactory.instance.createUnionTypeNode(
									...[NodeClassFactory.instance.createLiteralTypeNode(nodeClassDef.id)].concat(
										!nodeClassDef.equivalents || nodeClassDef.equivalents.length == 0
											? []
											: nodeClassDef.equivalents?.map(p =>
													factory.createLiteralTypeNode(factory.createStringLiteral(p))
												)
									)
								)
							: NodeClassFactory.instance.createLiteralTypeNode(nodeClassDef.id),
						undefined
					),
					factory.createConstructorDeclaration(
						undefined,
						[
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('isy'),
								undefined,
								factory.createTypeReferenceNode(factory.createIdentifier('ISY'), undefined),
								undefined
							),
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('nodeInfo'),
								undefined,
								factory.createTypeReferenceNode(factory.createIdentifier('NodeInfo'), [
									this.createTypeReferenceNode(Family[nodeClassDef.family], 'Family'),
								]),
								undefined
							),
						],
						factory.createBlock(
							[
								factory.createExpressionStatement(
									factory.createCallExpression(factory.createSuper(), undefined, [
										factory.createIdentifier('isy'),
										factory.createIdentifier('nodeInfo'),
									])
								),
								...Object.values(nodeClassDef.drivers).map(p =>
									NodeClassFactory.instance.createDriverInitializationStatement(p)
								),
							],
							true
						)
					),
					...Object.values(nodeClassDef.commands).map(p => NodeClassFactory.instance.createCommandMethodDeclaration(p)),
					...Object.values(nodeClassDef.drivers).map(p => NodeClassFactory.instance.createDriverGetDeclaration(p)),
				]
			),
			factory.createExpressionStatement(
				factory.createCallExpression(
					factory.createPropertyAccessExpression(
						factory.createIdentifier('NodeFactory'),
						factory.createIdentifier('register')
					),
					undefined,
					[factory.createIdentifier(`${nodeClassDef.name}`)]
				)
			),

			...(nodeClassDef.equivalents
				? nodeClassDef.equivalents?.map(p =>
						factory.createExpressionStatement(
							factory.createCallExpression(
								factory.createPropertyAccessExpression(
									factory.createIdentifier('NodeFactory'),
									factory.createIdentifier('register')
								),
								undefined,
								[factory.createIdentifier(`${nodeClassDef.name}`), factory.createStringLiteral(p)]
							)
						)
					)
				: []),
			factory.createModuleDeclaration(
				[factory.createToken(ts.SyntaxKind.ExportKeyword)],
				factory.createIdentifier(nodeClassDef.name),
				factory.createModuleBlock([
					factory.createInterfaceDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						factory.createIdentifier('Interface'),
						undefined,
						[
							factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
								factory.createExpressionWithTypeArguments(factory.createIdentifier('Omit'), [
									NodeClassFactory.instance.createTypeReferenceNode(
										'InstanceType',
										NodeClassFactory.instance.factory.createTypeQueryNode(
											factory.createIdentifier(`${nodeClassDef.name}`)
										)
									),
									factory.createTypeOperatorNode(
										ts.SyntaxKind.KeyOfKeyword,
										factory.createTypeReferenceNode(factory.createIdentifier('ISYDeviceNode'), [
											factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
											factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
											factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
											factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
										])
									),
								]),
							]),
						],
						[
							/*factory.createPropertySignature(
								undefined,
								factory.createIdentifier('nodeDefId'),
								undefined,
								nodeClassDef.equivalents ?
									NodeClassFactory.instance.createUnionTypeNode(
										...[NodeClassFactory.instance.createLiteralTypeNode(nodeClassDef.id)].concat(
											!nodeClassDef.equivalents || nodeClassDef.equivalents.length == 0 ? [] : nodeClassDef.equivalents?.map((p) => factory.createLiteralTypeNode(factory.createStringLiteral(p)))
										)
									)
								:	NodeClassFactory.instance.createLiteralTypeNode(nodeClassDef.id)
							)*/
						]
					),

					factory.createFunctionDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						undefined,
						factory.createIdentifier('is'),
						undefined,
						[
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('node'),
								undefined,
								factory.createTypeReferenceNode(factory.createIdentifier('ISYNode'), [
									factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
								]),
								undefined
							),
						],
						factory.createTypePredicateNode(
							undefined,
							factory.createIdentifier('node'),
							factory.createTypeReferenceNode(factory.createIdentifier(`${nodeClassDef.name}`), undefined)
						),
						NodeClassFactory.instance.createBlock(
							true,
							NodeClassFactory.instance.createReturnStatement(
								factory.createCallExpression(
									factory.createPropertyAccessExpression(
										factory.createArrayLiteralExpression(
											nodeClassDef.equivalents
												? [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)].concat(
														!nodeClassDef.equivalents || nodeClassDef.equivalents.length == 0
															? []
															: nodeClassDef.equivalents?.map(p => factory.createStringLiteral(p))
													)
												: [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)]
										),
										'includes'
									),
									undefined,
									[
										factory.createPropertyAccessExpression(
											factory.createIdentifier('node'),
											factory.createIdentifier('nodeDefId')
										),
									]
								)
							)
						)
					),
					factory.createFunctionDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						undefined,
						factory.createIdentifier('isImplementedBy'),
						undefined,
						[
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('node'),
								undefined,
								NodeClassFactory.instance.createTypeReferenceNode(
									'ISYNode',
									NodeClassFactory.instance.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									NodeClassFactory.instance.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									NodeClassFactory.instance.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
									NodeClassFactory.instance.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
								),
								undefined
							),
						],
						factory.createTypePredicateNode(
							undefined,
							factory.createIdentifier('node'),
							factory.createTypeReferenceNode(factory.createIdentifier(`${nodeClassDef.name}`), undefined)
						),
						NodeClassFactory.instance.createBlock(
							true,

							factory.createReturnStatement(
								factory.createCallExpression(
									factory.createPropertyAccessExpression(
										factory.createArrayLiteralExpression(
											nodeClassDef.implementedBy
												? [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)].concat(
														!nodeClassDef.implementedBy || nodeClassDef.implementedBy.length == 0
															? []
															: nodeClassDef.implementedBy?.map(p => factory.createStringLiteral(p))
													)
												: [NodeClassFactory.instance.createStringLiteral(nodeClassDef.id)]
										),
										'includes'
									),
									undefined,
									[
										factory.createPropertyAccessExpression(
											factory.createIdentifier('node'),
											factory.createIdentifier('nodeDefId')
										),
									]
								)
							)
						)
					),
					factory.createFunctionDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						undefined,
						factory.createIdentifier('create'),
						undefined,
						[
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('isy'),
								undefined,
								factory.createTypeReferenceNode(factory.createIdentifier('ISY'), undefined),
								undefined
							),
							factory.createParameterDeclaration(
								undefined,
								undefined,
								factory.createIdentifier('nodeInfo'),
								undefined,
								factory.createTypeReferenceNode(factory.createIdentifier('NodeInfo'), [
									this.createTypeReferenceNode(Family[nodeClassDef.family], 'Family'),
								]),
								undefined
							),
						],
						undefined,
						factory.createBlock(
							[
								factory.createReturnStatement(
									factory.createNewExpression(factory.createIdentifier(`${nodeClassDef.name}`), undefined, [
										factory.createIdentifier('isy'),
										factory.createIdentifier('nodeInfo'),
									])
								),
							],
							true
						)
					),
					factory.createVariableStatement(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						factory.createVariableDeclarationList(
							[
								factory.createVariableDeclaration(
									factory.createIdentifier('Node'),
									undefined,
									undefined,
									factory.createIdentifier(`${nodeClassDef.name}`)
								),
							],
							ts.NodeFlags.Const
						)
					),
					this.createVariableStatement(
						'Class',

						factory.createIdentifier(`${nodeClassDef.name}`),
						true
					),

					this.createModuleDeclaration(
						'Commands',
						this.createModuleBlock(
							factory.createTypeAliasDeclaration(
								[factory.createToken(ts.SyntaxKind.ExportKeyword)],
								factory.createIdentifier('Type'),
								undefined,
								factory.createTypeLiteralNode([
									...Object.values(nodeClassDef.commands).map(NodeClassFactory.instance.createCommandSignature),
								])
							)
						),
						ts.ModifierFlags.Export
					),
					this.factory.createEnumDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						factory.createIdentifier('Commands'),
						[
							...Object.values(nodeClassDef.commands).map(p =>
								factory.createEnumMember(p.name, this.createLiteral(p.id))
							),
						]
					),
					this.createModuleDeclaration(
						'Drivers',
						this.createModuleBlock(
							factory.createTypeAliasDeclaration(
								[factory.createToken(ts.SyntaxKind.ExportKeyword)],
								factory.createIdentifier('Type'),
								undefined,
								factory.createTypeLiteralNode([
									...Object.values(nodeClassDef.drivers).map(NodeClassFactory.instance.createDriverSignature),
								])
							)
						),
						ts.ModifierFlags.Export
					),
					this.factory.createEnumDeclaration(
						[factory.createToken(ts.SyntaxKind.ExportKeyword)],
						factory.createIdentifier('Drivers'),
						[
							...Object.values(nodeClassDef.drivers).map(p =>
								factory.createEnumMember(p.name, this.createLiteral(p.id))
							),
						]
					),
				]),
				ts.NodeFlags.Namespace
			),
		];
		//let f = createWrappedNode(sf);
		let p = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
		// p.printFile()

		let f = NodeClassFactory.project.createSourceFile(path, p.printFile(sf), { overwrite: true });
		f.formatText({
			indentSize: 1,
			insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
			insertSpaceAfterCommaDelimiter: true,
			insertSpaceAfterSemicolonInForStatements: true,
			insertSpaceBeforeAndAfterBinaryOperators: true,
			insertSpaceAfterConstructor: true,
			newLineCharacter: '\n',
			trimTrailingWhitespace: true,
			ensureNewLineAtEndOfFile: true,
			indentStyle: ts.IndentStyle.Smart,
		});
		f.fixUnusedIdentifiers();

		let currentKind = ts.SyntaxKind.Unknown;
		for (const s of f.getStatements()) {
			if (currentKind.valueOf() != s.getKind().valueOf()) {
				if (currentKind != ts.SyntaxKind.Unknown) {
					s.prependWhitespace('\n');
				}
				currentKind = s.getKind().valueOf();
			}
		}
		f.insertText(0, '/* THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT DIRECTLY. */\n\n');
		return {
			family: nodeClassDef.family,
			name: nodeClassDef.name,
			id: nodeClassDef.id,
			path,
			statements: sf.statements,
			sourceFile: f,
		};
	}

	public static generateAll(): { family: Family; path: string; statements: ts.Statement[] }[] {
		let modules = [];
		for (const family of NodeClassDefinition.Map.keys()) {
			var t = this.generateClassesForFamily(family);
			modules.push(NodeClassFactory.buildClassIndex(family, t));
		}
		return modules;
	}

	public static generateClassesForFamily<T extends Family>(family: T): GeneratedNodeClass<T>[] {
		return NodeClassFactory.buildNodeClasses<T>(
			NodeClassDefinition.Map.get(family) as { [x: string]: NodeClassDefinition<T> }
		);
	}

	// #region Functions (12)

	static buildClassIndex<T extends Family>(family: T, classes: GeneratedNodeClass<T>[]) {
		return {
			family,
			path: `/${Family[family]}/Generated/index.ts`,
			statements: [
				...classes.map(p =>
					factory.createExportDeclaration(
						undefined,
						false,
						undefined,
						factory.createStringLiteral(`./${p.name}.js`),
						undefined
					)
				),
			],
		};
	}

	createCommandArguments(def: CommandDefinition) {
		let c = [factory.createStringLiteral(def.id)];
		let n = [];
		let r: ParameterDefinition[] = [];

		if (def.parameters) {
			for (let p of def.parameters) {
				let p2 = p;
				if (!p2?.id || p2.id == 'value') {
					n.push(this.createUOMParameterArgument(p2));
					//.push(factory.createIdentifier(p2.name ?? 'value'));
				} else {
					r.push(p2);
				}
			}
		}
		let fnl = [
			...c,
			...n,

			...(r.length > 0
				? [
						factory.createObjectLiteralExpression(
							r.map(q =>
								factory.createPropertyAssignment(
									factory.createIdentifier(q.id ?? 'value'),
									this.createUOMParameterArgument(q)
								)
							),
							false
						),
					]
				: []),
		];
		return fnl.length > 0 ? fnl : undefined;
	}

	createUOMParameterArgument(parent: ParameterDefinition) {
		if (parent.dataType.length == 1) {
			let dt = parent.dataType[0];
			return factory.createArrayLiteralExpression([
				factory.createIdentifier(parent.name),
				factory.createPropertyAccessChain(
					factory.createIdentifier('UnitOfMeasure'),
					undefined,
					factory.createIdentifier(UnitOfMeasure[dt.uom] ?? 'Unknown')
				),
			]);
		}
		return factory.createIdentifier(parent.name ?? 'value');
	}

	createCommandMethodArguments(def: CommandDefinition): ts.ParameterDeclaration[] {
		let p1: ts.ParameterDeclaration[] = [];
		let p2: ts.ParameterDeclaration[] = [];

		for (let p of def.parameters) {
			if (p.optional) {
				p2.push(this.createParameterDeclarationSignature(p));
			} else {
				p1.push(this.createParameterDeclarationSignature(p));
			}
		}

		return [...p1, ...p2];
	}

	createCommandSignatureArguments(def: CommandDefinition): ts.ParameterDeclaration[] {
		let p1: ts.ParameterDeclaration[] = [];
		let p2: ts.ParameterDeclaration[] = [];

		for (let p of def.parameters) {
			if (p.optional) {
				p2.push(this.createParameterSignature(p));
			} else {
				p1.push(this.createParameterSignature(p));
			}
		}

		return [...p1, ...p2];
	}

	createCommandMethodDeclaration(def: CommandDefinition) {
		return factory.createMethodDeclaration(
			[factory.createToken(ts.SyntaxKind.AsyncKeyword)],
			undefined,
			factory.createIdentifier(def.name),
			undefined,
			undefined,
			def.parameters ? this.createCommandMethodArguments(def) : [],
			undefined,
			factory.createBlock([
				factory.createReturnStatement(
					factory.createCallExpression(
						factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier('sendCommand')),
						undefined,
						NodeClassFactory.instance.createCommandArguments(def)
					)
				),
			])
		);
	}

	addUOMType(def: DataTypeDefinition, typeNode: ts.TypeNode) {
		return factory.createTupleTypeNode([
			typeNode,
			factory.createTypeReferenceNode(
				factory.createQualifiedName(
					factory.createIdentifier('UnitOfMeasure'),
					factory.createIdentifier(UnitOfMeasure[def.uom])
				),
				undefined
			),
		]);
	}

	/*addMaybeUOMType(def: DataTypeDefinition, typeNode: ts.TypeNode) {
		return factory.createTypeParameterDeclaration(typeNode, factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier('UnitOfMeasure'), factory.createIdentifier(UnitOfMeasure[def.uom])), undefined));
	}*/

	createCommandParameterType(def: DataTypeDefinition, parent: ParameterDefinition): ts.TypeNode {
		if (def.enum) {
			{
				if (EnumDefinitionMap.has(parent.classDef.family)) {
					var enumDef = EnumDefinitionMap.get(parent.classDef.family)[def.indexId];

					// ?? EnumDefinitionMap[Family.Global]?[def.indexId]
					if (enumDef) {
						return factory.createTypeReferenceNode(
							factory.createQualifiedName(
								factory.createIdentifier(Family[parent.classDef.family]),
								factory.createIdentifier(enumDef.name)
							),
							undefined
						);
					}
				}

				if (def.values) {
					return NodeClassFactory.instance.createUnionTypeNode(
						...Object.keys(def.values).map(p => factory.createLiteralTypeNode(factory.createNumericLiteral(p)))
					);
				}

				//   return NodeClassFactory.instance.createUnionTypeNode(...
				//     Object.values(def.values).map((p) => factory.createTypeReferenceNode(
				//   factory.createQualifiedName(
				//     factory.createIdentifier("UnitOfMeasure"),
				//     factory.createIdentifier(UnitOfMeasure)
				//   ),
				//   undefined
				// )))
			}
		}
		return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
	}

	createCommandSignature(def: CommandDefinition) {
		const that = this;
		return factory.createPropertySignature(
			undefined,
			factory.createIdentifier(def.id),
			undefined,
			factory.createIntersectionTypeNode([
				factory.createParenthesizedType(
					factory.createFunctionTypeNode(
						undefined,
						def.parameters ? NodeClassFactory.instance.createCommandSignatureArguments(def) : [],

						factory.createTypeReferenceNode(factory.createIdentifier('Promise'), [
							factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
						])
					)
				),
				factory.createTypeLiteralNode([
					factory.createPropertySignature(
						undefined,
						factory.createIdentifier('label'),
						undefined,
						factory.createLiteralTypeNode(factory.createStringLiteral(def.label))
					),
					factory.createPropertySignature(
						undefined,
						factory.createIdentifier('name'),
						undefined,
						factory.createLiteralTypeNode(factory.createStringLiteral(def.name))
					),
				]),
			])
		);
	}

	createDriverGetDeclaration(def: DriverDefinition) {
		return factory.createGetAccessorDeclaration(
			[factory.createToken(ts.SyntaxKind.PublicKeyword)],
			factory.createIdentifier(def.name),
			[],
			NodeClassFactory.instance.createUnionTypeNode(
				...def.dataType?.map(p => NodeClassFactory.instance.createDriverReturnType(p, def))
			),
			factory.createBlock(
				[
					factory.createReturnStatement(
						factory.createPropertyAccessChain(
							factory.createPropertyAccessExpression(
								factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier('drivers')),
								factory.createIdentifier(def.id)
							),
							factory.createToken(ts.SyntaxKind.QuestionDotToken),
							factory.createIdentifier('value')
						)
					),
				],
				true
			)
		);
	}

	createDriverInitializationStatement(def: DriverDefinition): ts.Statement {
		return factory.createExpressionStatement(
			factory.createBinaryExpression(
				factory.createPropertyAccessExpression(
					factory.createPropertyAccessExpression(factory.createThis(), factory.createIdentifier('drivers')),
					factory.createIdentifier(def.id)
				),
				factory.createToken(ts.SyntaxKind.EqualsToken),
				factory.createCallExpression(
					factory.createPropertyAccessExpression(
						factory.createIdentifier('Driver'),
						factory.createIdentifier('create')
					),
					undefined,
					[
						factory.createStringLiteral(def.id),
						factory.createThis(),
						factory.createElementAccessExpression(
							factory.createPropertyAccessExpression(
								factory.createIdentifier('nodeInfo'),
								factory.createIdentifier('state')
							),
							this.createStringLiteral(def.id)
						),
						factory.createObjectLiteralExpression(
							[
								factory.createPropertyAssignment(
									factory.createIdentifier('uom'),
									factory.createPropertyAccessExpression(
										factory.createIdentifier('UnitOfMeasure'),
										factory.createIdentifier(UnitOfMeasure[def.dataType[0]?.uom] ?? 'Unknown')
									)
								),
								factory.createPropertyAssignment(
									factory.createIdentifier('label'),
									factory.createStringLiteral(def.label)
								),
								factory.createPropertyAssignment(
									factory.createIdentifier('name'),
									factory.createStringLiteral(def.name)
								),
							],
							false
						),
					]
				)
			)
		);
	}

	createDriverPropertyReturnType(def: DataTypeDefinition, parent: DriverDefinition): ts.TypeNode {
		if (def.enum) {
			{
				if (EnumDefinitionMap.has(parent.classDef.family)) {
					var enumDef = EnumDefinitionMap.get(parent.classDef.family)[def.indexId];

					// ?? EnumDefinitionMap[Family.Global]?[def.indexId]
					if (enumDef) {
						return factory.createTypeReferenceNode(
							factory.createQualifiedName(
								factory.createIdentifier(Family[parent.classDef.family]),
								factory.createIdentifier(enumDef.name)
							),
							undefined
						);
					}
				}
				if (def.values) {
					return NodeClassFactory.instance.createUnionTypeNode(
						...Object.keys(def.values).map(p => factory.createLiteralTypeNode(factory.createNumericLiteral(p)))
					);
				}

				//   return NodeClassFactory.instance.createUnionTypeNode(...
				//     Object.values(def.values).map((p) => factory.createTypeReferenceNode(
				//   factory.createQualifiedName(
				//     factory.createIdentifier("UnitOfMeasure"),
				//     factory.createIdentifier(UnitOfMeasure)
				//   ),
				//   undefined
				// )))
			}
		}
		if (
			'min' in def &&
			'max' in def &&
			def.max < 1000 &&
			def.min >= 0 &&
			Number.isInteger(def.min) &&
			Number.isInteger(def.max)
		) {
			return factory.createTypeReferenceNode(factory.createIdentifier('IntRange'), [
				factory.createLiteralTypeNode(this.createLiteral(def.min)),
				factory.createLiteralTypeNode(this.createLiteral(def.max)),
			]);
		}
		return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
	}

	createDriverSignature(def: DriverDefinition) {
		return factory.createPropertySignature(
			undefined,
			factory.createIdentifier(def.id),
			undefined,
			factory.createTypeLiteralNode([
				factory.createPropertySignature(
					undefined,
					factory.createIdentifier('uom'),
					undefined,
					def.dataType
						? NodeClassFactory.instance.createUnionTypeNode(
								...def.dataType?.map(p => NodeClassFactory.instance.createTypeNodeForUOM(p.uom))
							)
						: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
				),
				factory.createPropertySignature(
					undefined,
					factory.createIdentifier('value'),
					undefined,
					def.dataType
						? NodeClassFactory.instance.createUnionTypeNode(
								...def.dataType.map(p => NodeClassFactory.instance.createDriverReturnType(p, def))
							)
						: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
				),
				factory.createPropertySignature(
					undefined,
					factory.createIdentifier('label'),
					undefined,
					factory.createLiteralTypeNode(factory.createStringLiteral(def.label))
				),
				factory.createPropertySignature(
					undefined,
					factory.createIdentifier('name'),
					undefined,
					factory.createLiteralTypeNode(factory.createStringLiteral(def.name))
				),
			])
		);
	}

	createDriverReturnType(def: DataTypeDefinition, parent: DriverDefinition): ts.TypeNode {
		if (def.enum) {
			{
				if (EnumDefinitionMap.has(parent.classDef.family)) {
					var enumDef = EnumDefinitionMap.get(parent.classDef.family)[def.indexId];

					// ?? EnumDefinitionMap[Family.Global]?[def.indexId]
					if (enumDef) {
						return factory.createTypeReferenceNode(
							factory.createQualifiedName(
								factory.createIdentifier(Family[parent.classDef.family]),
								factory.createIdentifier(enumDef.name)
							),
							undefined
						);
					}
				}
				if (def.values) {
					return NodeClassFactory.instance.createUnionTypeNode(
						...Object.keys(def.values).map(p => factory.createLiteralTypeNode(factory.createNumericLiteral(p)))
					);
				}

				//   return NodeClassFactory.instance.createUnionTypeNode(...
				//     Object.values(def.values).map((p) => factory.createTypeReferenceNode(
				//   factory.createQualifiedName(
				//     factory.createIdentifier("UnitOfMeasure"),
				//     factory.createIdentifier(UnitOfMeasure)
				//   ),
				//   undefined
				// )))
			}
		}
		if ('min' in def && 'max' in def && def.max < 1000 && def.min >= 0) {
			return factory.createTypeReferenceNode(factory.createIdentifier('IntRange'), [
				factory.createLiteralTypeNode(this.createLiteral(def.min)),
				factory.createLiteralTypeNode(this.createLiteral(def.max)),
			]);
		}
		return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
	}

	createParameterDeclarationSignature(def: ParameterDefinition) {
		return factory.createParameterDeclaration(
			undefined,
			undefined,
			factory.createIdentifier(def.name ?? 'value'),
			def.optional ? factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
			def.dataType
				? NodeClassFactory.instance.createUnionTypeNode(
						...def.dataType?.map(p => NodeClassFactory.instance.createCommandParameterType(p, def))
					)
				: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
			undefined
		);
	}

	createParameterSignature(def: ParameterDefinition) {
		return factory.createParameterDeclaration(
			undefined,
			undefined,
			factory.createIdentifier(def.id ?? 'value'),
			def.optional ? factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
			def.dataType
				? NodeClassFactory.instance.createUnionTypeNode(
						...def.dataType?.map(p => NodeClassFactory.instance.createCommandParameterType(p, def))
					)
				: factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
			undefined
		);
	}

	createTypeNodeForUOM(uom: number): ts.TypeNode {
		return NodeClassFactory.instance.createTypeReferenceNode(UnitOfMeasure[uom] ?? 'Unknown', 'UnitOfMeasure');
	}

	// #endregion Public Static Methods (4)
}

// #endregion Classes (1)
// #endregion Functions (12)
