import { Family } from '../Definitions/index.js';
import { ISY, writeDebugFile } from '../ISY.js';
import { ISYNode } from '../ISYNode.js';
import type { NodeDef } from '../Model/NodeDef.js';
import { type NodeInfo, isDynamic, isZWave } from '../Model/NodeInfo.js';
import type { Factory, StringKeys } from '../Utils.js';
import type { Constructor } from './Constructor.js';
import type { DynamicNode } from './DynamicNode.js';
import type { ISYDeviceNode } from './ISYDeviceNode.js';

export namespace NodeFactory {
	export type NodeClassFactory<T extends ISYDeviceNode<any, any, any, any>> = Factory<T> & { implements: string[] } & {
		Commands;
		Drivers;
	};
	export const registry: NodeClassRegistry = {};

	export type NodeClass<T extends Family | keyof typeof Family> = T extends Family
		? typeof ISYNode<T, any, any, any>
		: T extends keyof typeof Family
			? typeof ISYNode<(typeof Family)[T], any, any, any>
			: never;

	export const implementsRegistry: { [x in StringKeys<typeof Family>]?: { [x: string]: string[] } } = {};

	export function register<F extends keyof typeof Family>(nodeClass: NodeClass<F>, id: string = nodeClass.nodeDefId) {
		//let s; //FamilyNodeClassRegistry<(typeof Family)[F]>;
		let f = Family[nodeClass.family];
		let s = (registry[f] ?? (registry[f] = {})) as FamilyNodeClassRegistry<any>;
		s[id] = nodeClass;
		if (!implementsRegistry[f]) {
			implementsRegistry[f] = {};
		}
		implementsRegistry[f][id] = nodeClass.implements;
	}

	function compare(a: typeof ISYNode<any, any, any, any>, b: typeof ISYNode<any, any, any, any>) {
		if (a.nodeDefId === b.nodeDefId) {
			return 0;
		}
		if (a.implements.includes(b.nodeDefId)) return 1;
		if (b.implements.includes(a.nodeDefId)) return -1;
		return b.nodeDefId.localeCompare(a.nodeDefId);
	}

	export function sortImplementsRegistry() {
		for (let f in implementsRegistry) {
			let reg = implementsRegistry[f];
			for (let e in reg) {
				reg[e] = reg[e].sort((a, b) =>
					compare(getForNodeDefId(f as keyof typeof Family, a), getForNodeDefId(f as keyof typeof Family, b))
				);
			}
		}
	}

	export function getImplements<F extends keyof typeof Family, T extends ISYNode<(typeof Family)[F], any, any, any>>(
		node: T | typeof ISYNode | Factory<ISYNode>
	): string[] {
		if (node instanceof ISYNode) return implementsRegistry[Family[node.family] as F][node.nodeDefId] ?? [];
		else if (typeof node === 'function') return implementsRegistry[Family[node.family] as F][node.nodeDefId] ?? [];
		else
			return (
				implementsRegistry[Family[(node.Class as typeof ISYNode).family] as F][
					(node.Class as typeof ISYNode).nodeDefId
				] ?? []
			);
	}

	export function getForNode<F extends keyof typeof Family>(
		family: F,
		node: NodeInfo<(typeof Family)[F]>
	): NodeClass<F> {
		if (isZWave(node)) {
			return getForNodeDefId(family, node.sgid ?? node.nodeDefId);
		} else if (isDynamic(node)) {
			return getForNodeDefId(family, node.nodeTypeId);
		}
		return getForNodeDefId(family, node.nodeDefId);
	}

	export function getForNodeDefId<F extends keyof typeof Family | Family>(family: F, nodeDefId: string): NodeClass<F> {
		if (typeof family === 'string') {
			return registry[family as keyof typeof Family]?.[nodeDefId] as NodeClass<F>;
		} else if (typeof family === 'number') {
			return registry[Family[family] as keyof typeof Family]?.[nodeDefId] as NodeClass<F>;
		}
	}

	export async function get<F extends keyof typeof Family>(
		node: NodeInfo<(typeof Family)[F]>,
		isy: ISY = ISY.instance
	): Promise<typeof ISYNode<(typeof Family)[F], any, any, any>> {
		let nodeN = null;

		let family = Family.Insteon;
		if (typeof node.family === 'object') family = node.family._;
		else if (node.family !== undefined && node.family !== null) family = node.family;

		nodeN = getForNodeDefId(Family[family] as F, node.nodeDefId);
		if (nodeN) return Promise.resolve(nodeN);

		let cls = (await import('../Devices/GenericNode.js')).GenericNode;
		NodeFactory.register(cls, isDynamic(node) || isZWave(node) ? node.nodeTypeId : node.nodeDefId);
		return cls;
	}

	export async function create<F extends Family>(
		nodeInfo: NodeInfo<F>,
		isy: ISY = ISY.instance
	): Promise<ISYNode<F, any, any, any>> {
		const nodeClass = (await get(nodeInfo)) as unknown as Constructor<ISYNode<F, any, any, any>>;
		let nodeInstance = null as ISYNode<F, any, any, any>;
		if (nodeClass?.name !== 'ISYNode' && nodeClass !== undefined) {
			nodeInstance = new nodeClass(isy, nodeInfo);
		}

		if (isDynamic(nodeInfo) || isZWave(nodeInfo)) {
			let n = (await isy.sendRequest(
				`zmatter/${nodeInfo.family == Family.ZWave ? 'zwave' : 'zigbee'}/node/${nodeInfo.address}/def/get?full=true`,
				{ trailingSlash: false }
			)) as NodeDef;
			if (n) {
				isy.logger.info(
					`NodeDef for ${nodeInfo.name} with nodeDef ${nodeInfo.nodeDefId} is dynamic. Saving nodeDef for debug purposes.`
				);
				if (isy.isDebugEnabled)
					await writeDebugFile(JSON.stringify(n), `${nodeInfo.address}_NodeDef.json`, isy.logger, isy.storagePath);
				if (nodeInstance) {
					try {
						(nodeInstance as DynamicNode<any, any, any, any>).applyNodeDef(n);
					} catch {}
				}
			} else {
				/*ISY.instance.logger.warn(`No built-in class found for ${Family[nodeInfo.family ?? 1]}.${nodeInfo.nodeDefId}`);
			return new GenericNode(isy, nodeInfo);*/
			}
		}
		return nodeInstance;
	}
}
export type FamilyNodeClassRegistry<T extends Family> = { [x: string]: NodeFactory.NodeClass<T> };

type NodeClassRegistry = { [x in Extract<keyof typeof Family, string>]?: FamilyNodeClassRegistry<(typeof Family)[x]> };
