import { Family } from './Definitions/index.js';
import { NodeFactory } from './Devices/NodeFactory.js';
import { ISYDevice } from './ISYDevice.js';
import { ISYNode } from './ISYNode.js';
import type { StringKeys } from './Utils.js';

export class Registry<K extends keyof any, T> {
	Global: { [x in K]?: T } = {};
	FamilyLevel: { [x in StringKeys<typeof Family>]?: { [y in K]?: T } } = {};

	DeviceLevel: { [x: string]: { [y in K]?: T } } = {};
	NodeDefLevel: { [x: string]: { [y in K]?: T } } = {};

	ClassLevel: { [x: string]: { [y in K]?: T } } = {};

	registerGlobal(key: K, value: T): void {
		this.Global[key] = value;
	}

	registerFamilyLevel<F extends Family>(family: F & number, key: K, value: T): void {
		this.FamilyLevel[Family[family] as string] = this.FamilyLevel[Family[family] as string] ?? {};
		this.FamilyLevel[Family[family] as string]![key] = value;
	}

	registerDeviceLevel(device: string, key: K, value: T): void {
		this.DeviceLevel[device] = this.DeviceLevel[device] ?? {};
		this.DeviceLevel[device]![key] = value;
	}

	registerNodeDefLevel(nodeDefId: string, key: K, value: T): void {
		this.NodeDefLevel[nodeDefId] = this.NodeDefLevel[nodeDefId] ?? {};
		this.NodeDefLevel[nodeDefId]![key] = value;
	}

	registerClassLevel(className: string, key: K, value: T): void {
		this.ClassLevel[className] = this.ClassLevel[className] ?? {};
		this.ClassLevel[className]![key] = value;
	}

	register(key: K, value: T): void;
	register(key: K, value: T, deviceType: string): void;
	register(key: K, value: T, factory: ISYDevice.Factory<any, any, any, any, any>): void;
	register(key: K, value: T, discriminant?: string | Family | ISYDevice.Factory<any, any, any, any, any>): void {
		if (discriminant === undefined) {
			this.registerGlobal(key, value);
			return;
		} else if (typeof discriminant === 'string') {
			this.registerDeviceLevel(discriminant, key, value);
			return;
		} else if (typeof discriminant === 'number') {
			if (discriminant in Family) {
				this.registerFamilyLevel(discriminant as Family, key, value);
				return;
			}
		} else if (ISYDevice.isNode(discriminant)) {
			this.registerNodeDefLevel(discriminant.Class.prototype.nodeDefId, key, value);
			for (const nodeDefId in NodeFactory.getImplements(discriminant)) {
				this.registerNodeDefLevel(nodeDefId, key, value);
			}
			this.registerClassLevel(discriminant.Class.name, key, value);
			this.registerFamilyLevel((discriminant.Class as unknown as typeof ISYNode).family, key, value);
			return;
		} else if (ISYDevice.isComposite(discriminant)) {
			this.registerClassLevel(discriminant.Class.name, key, value);

			return;
		}
		/*else if(ISYDevice.isComposite(discriminant))
		{
			this.registerFamilyLevel(discriminant.types, key, value);
			return;
		}*/ //TODO: Implement Composite Device
	}

	getGlobal(key: K): T | undefined {
		return this.Global[key];
	}

	getFamilyLevel<F extends Family>(family: F & number, key: K): T | undefined {
		return this.FamilyLevel[Family[family] as string]?.[key];
	}

	getDeviceLevel(device: string, key: K): T | undefined {
		return this.DeviceLevel[device]?.[key];
	}

	getNodeLevel(nodeDefId: string, key: K): T | undefined {
		return this.NodeDefLevel[nodeDefId]?.[key];
	}

	getClassLevel(className: string, key: K): T | undefined {
		return this.ClassLevel[className]?.[key];
	}

	get(device: ISYDevice.Any, key: K): T | undefined {
		if (device.type) {
			let d = this.getDeviceLevel(device.type, key);
			if (d) return d;
		}
		if (ISYDevice.isNode(device)) {
			let d = this.getNodeLevel(device.nodeDefId, key);
			if (d) {
				return d;
			} else {
				for (const nodeDefId in NodeFactory.getImplements(device)) {
					let d = this.getNodeLevel(nodeDefId, key);
					if (d) return d;
				}
			}
		}
		if (ISYDevice.isComposite(device)) {
			let d = this.getClassLevel(device.constructor.name, key);
			if (d) return d;
		}
		let f = this.getFamilyLevel(device.family, key);
		if (f) return f;
		return this.getGlobal(key);
	}
}
