import { ClusterBehavior, MutableEndpoint, SupportedBehaviors } from '@matter/main';
import { type FixedLabelBehavior, type UserLabelBehavior, BridgedDeviceBasicInformationBehavior, DescriptorServer, FixedLabelServer, UserLabelServer } from '@matter/node/behaviors';
import { Devices } from 'isy-nodejs/ISY';

import { Feature } from 'isy-nodejs/Definitions/Global/Features';

import type { CamelCase, JsonPrimitive, Simplify, StringKeyOf } from 'type-fest';
//import type { FanFanDevice } from 'isy-nodejs/Devices/Insteon/InsteonFanDevice';

import { Descriptor } from '@matter/main/clusters';
import type { ClusterType } from '@matter/main/types';
import type { Converter } from 'isy-nodejs/Converters';
import { Devices as DevicesNS, Family } from 'isy-nodejs/ISY';
import { ISYDevice } from 'isy-nodejs/ISYDevice';
import { CommandsOf, DriversOf, ISYNode } from 'isy-nodejs/ISYNode';
import { ISYBridgedDeviceBehavior } from '../Behaviors/ISYBridgedDeviceBehavior.js';

// #region Type aliases (16)

export type AttributeMapping<B, D> = B extends { cluster: { attributes: infer E extends { [K in string]: ClusterType.Attribute } } } ? Partial<Record<keyof E, keyof DriversOf<D> | { driver: keyof DriversOf<D>; converter?: Converter.KnownConverters }>> : never;
export type ClusterMapping<B, T> =
	B extends { cluster } ?
		{
			attributes?: ClusterAttributeMapping<B['cluster'], T>;
			commands?: ClusterCommandMapping<B['cluster'], T>;
		}
	:	{ attributes?: ClusterAttributeMapping<unknown, T>; commands?: ClusterCommandMapping<unknown, T> };
export type ClusterAttributeMapping<A, K> = {
	[key in keyof ClusterType.AttributesOf<A>]?: DriverMapping<K>;
};

export type DriverMapping<K> =
	| {
			driver: ISYDevice.DriverNamesOf<K>;
			converter?: Converter.KnownConverters | ((value: any) => JsonPrimitive);
			node?: string;
	  }
	| ISYDevice.DriverNamesOf<K>
	| { value: string | number | boolean };

// export type ClusterTypeCommandMapping<A extends ClusterType, K> = {
//   [key in keyof Clusters.ClusterType.CommandsOf<ToCompleteClusterByName<A>>]?:
//     | { command: CommandsOf<K>; parameters?: parameterMapping }
//     | CommandsOf<K>
// };
export type ClusterCommandMapping<A, K> = {
	[key in keyof ClusterType.CommandsOf<A>]?: { command: ISYDevice.CommandNamesOf<K>; parameters?: parameterMapping } | ISYDevice.CommandNamesOf<K>;
};
//export type FamilyToDeviceMap<T extends Family> = Record<keyof Devices<T>, DeviceToClusterMap<ISYDevice<T>>>;

// export type ClusterTypeMapping<A extends ClusterType,K> = {
//     attributes: ClusterTypeAttributeMapping<A,K>,
//     commands: ClusterTypeCommandMapping<A,K>
// };
/*export type ClusterMapping<A, K> = {
	attributes: ClusterAttributeMapping<A, K>;
	commands: ClusterCommandMapping<A, K>;
};*/
export type CommandMapping<B, D> =
	B extends (
		{
			cluster: { commands: infer E extends { [K in string]: ClusterType.Command } };
		}
	) ?
		Partial<Record<keyof E, keyof ISYNode.CommandsOf<D> | { command: keyof ISYNode.CommandsOf<D>; parameters?: parameterMapping }>>
	:	never;
// `${const ClusterList = Object.keys(Clusters).filter(p => p instanceof Clusters.Cluster).map(p => p.constructor.name);
// var s = {...ClusterList};

// type ClusterName = ClusterList[0] | ClusterList[1];

// var ColorControl : ClusterName = "ColorControl";}`

//  type ChildrenOf<T> = T extends Family.Global ? Family | ISYDevice | string :
//                                     T extends Family ? ISYDevice | string  : string;

// export type ClusterMap<T extends Family | ISYDevice<Family> | string> = T extends ISYDevice<Family>
//   ? {
//       deviceType: Partial<Device>;
//       scope?: ChildrenOf<T>;
//       mapping: [ClusterTypeMapping<ClusterType,T>];
//       behavior?: typeof ClusterBehavior;
//     }
//   : {
//       scope?: ChildrenOf<T>;
//       mapping: [ClusterTypeMapping<ClusterType,any>];
//       behavior?: typeof ClusterBehavior;
//     };

//ype GenericCluster = Clusters.OnOffCluster | Clusters.ColorControl.Complete | Clusters.LevelControl.Cluster;

// export type DeviceToClusterMap<T extends ISYDevice<Family,any,any>> =
// {
//     deviceType: Partial<Device>;
//     scope?: string;
//     mapping: {[Type in ClusterType]?:ClusterTypeMapping<Type,T>};
//     behavior?: typeof ClusterBehavior;

// }
export interface DeviceToClusterMap<T, D extends MutableEndpoint> {
	deviceType: D;
	mapping: Simplify<EndpointMapping<D, T>>;
}

export interface Mapping<T extends ISYNode, D extends MutableEndpoint> {
	deviceType: D;

	features: Feature;

	nodeType?: T;
	mapping?: {
		[K in keyof D['behaviors']]?: {
			attributes?: {
				[K2 in keyof ClusterType.AttributesOf<D['behaviors'][K]>]: { driver: keyof DriversOf<T>; converter?: Converter.KnownConverters } | keyof DriversOf<T>;
			};
			commands?: {
				[K2 in keyof ClusterType.CommandsOf<D['behaviors'][K]>]: { command: keyof CommandsOf<T>; converter?: Converter.KnownConverters } | keyof CommandsOf<T>;
			};
		};
	};
}

export type EndpointMapping<A extends MutableEndpoint, D> = {
	[K in StringKeyOf<A['requirements']['server']['mandatory']> as CamelCase<K>]?: ClusterMapping<A['requirements']['server']['mandatory'][K], D>;
} & {
	[K in StringKeyOf<A['requirements']['server']['optional']> as CamelCase<K>]?: ClusterMapping<A['requirements']['server']['optional'][K], D>;
} & {
	[K in StringKeyOf<A['requirements']['client']['mandatory']> as CamelCase<K>]?: ClusterMapping<A['requirements']['client']['mandatory'][K], D>;
} & {
	[K in StringKeyOf<A['requirements']['client']['optional']> as CamelCase<K>]?: ClusterMapping<A['requirements']['client']['optional'][K], D>;
};

/*&
	{[K in CamelCase<ExtractKeys<A['requirements']['server']['optional'], { cluster }}>>]?: Simplify<
		ClusterMapping<
			//@ts-expect-error
			A['requirements']['server']['optional'],
			D
		>
	>}>*/
//{
/*attributes?: AttributeMapping<A['behaviors'][Uncapitalize<K>], D>;
		commands?: CommandMapping<A['behaviors'][Uncapitalize<K>], D>;*/
//}>;

////const ClusterIdentifier = Object.values(Clusters).filter(p=> p instanceof Clusters.MutableCluster && typeof p == "object" && p.constructor.name.endsWith(".Cluster"));
//type clusterList = keyof typeof ClusterIdentifier;

// export type ClusterMappings = {
//    OnOffCluster: ClusterTypeMapping<ClusterType.OnOff,ISYDevice<Family>>
// }

//| typeof ISYDevice<any, infer B, any>

// export type ClusterTypeAttributeMapping<A extends ClusterType, K> = {
//   [key in keyof Clusters.ClusterType.AttributesOf<ToCompleteClusterByName<A>>]?:
//     | { driver: DriversOf<K>; converter?: string }
//     | DriversOf<K>;
// };
export type EndpointMapping1<A extends MutableEndpoint, K> = {
	attributes?: SBAttributeMapping<A['behaviors'], K>;
	commands?: SBCommandMapping<A['behaviors'], K>;
};
//@ts-ignore

type Devices = typeof DevicesNS;

type SupportedFamily = keyof Devices & keyof typeof Family;
export type FamilyToClusterMap<T extends SupportedFamily> = { Family: T } & {
	[Type in keyof Devices[T]]?: DeviceToClusterMap<Devices[T][Type], MutableEndpoint>;
} & { add<D extends keyof Devices[T], M extends MutableEndpoint>(mapping: { [x in D]?: DeviceToClusterMap<Devices[T][x], M> }): FamilyToClusterMap<T> };

export function add<const F extends SupportedFamily, const Y extends keyof Devices[F], const D extends MutableEndpoint>(This: FamilyToClusterMap<F> & object, mapping: { [x in Y]?: DeviceToClusterMap<Devices[F][x], D> }): FamilyToClusterMap<F> {
	let m = { ...This, ...mapping } as FamilyToClusterMap<F> & object;
	//@ts-ignore
	let s = {
		...m,
		add<K extends keyof Devices[F], const M extends MutableEndpoint>(mapping2: { [x in K]?: DeviceToClusterMap<Devices[F][x], M> }) {
			return add<F, K, M>(m, mapping2);
		}
	};
	return s;
}

export function hasPath<X extends string, Y>(mapping: DriverMapping<any>): mapping is { driver: X; converter?: Y; node?: string } {
	if (typeof mapping == 'object' && 'driver' in mapping) {
		const driver = mapping.driver;
		if (typeof driver === 'string') {
			return true;
		}
	}
	return false;
}

export type SBAttributeMapping<SB extends SupportedBehaviors, D> = {
	[K in keyof SB]: Partial<Record<any, DriversOf<D> | { driver: DriversOf<D>; converter?: string }>>;
};
export type SBCommandMapping<SB extends SupportedBehaviors, D> = {
	//@ts-expect-error
	[K in Capitalize<keyof SB>]?: SB[Uncapitalize<K>] extends { cluster: { commands } } ? Partial<Record<string, CommandsOf<D> | { driver: DriversOf<D>; converter?: string }>> : never;
};

export type parameterMapping = {
	[key: string]: { parameter: string; converter?: string };
};

// #endregion Type aliases (16)

// #region Classes (1)
//@ts-ignore
const BaseDescriptorServer = DescriptorServer.withFeatures(Descriptor.Feature.TagList);

export class MappingRegistry {
	// #region Properties (1)

	public static map: Map<keyof typeof Family, Map<string, DeviceToClusterMap<ISYDevice.Any, MutableEndpoint>>> = new Map();

	public static cache: { [x: string]: DeviceToClusterMap<ISYDevice.Any, MutableEndpoint> } = {};

	// #endregion Properties (1)

	// #region Public Static Methods (3)

	public static getMapping<const T extends ISYDevice.Any = ISYDevice.Any, M extends MutableEndpoint = MutableEndpoint>(device: T): DeviceToClusterMap<T, MutableEndpoint.With<M, { isyNode: typeof ISYBridgedDeviceBehavior<T, M>; descriptor: typeof BaseDescriptorServer; bridgedDeviceBasicInformation: typeof BridgedDeviceBasicInformationBehavior; userLabel: typeof UserLabelBehavior; fixedLabel: typeof FixedLabelBehavior }>> {
		let m = this.cache[device.address];
		if (!m) {
			if (device) {
				if (MappingRegistry.map.has(Family[device.family] as keyof typeof Family)) {
					let g = MappingRegistry.map.get(Family[device.family] as keyof typeof Family);
					//let m: DeviceToClusterMap<T,MutableEndpoint>;
					if (g.has(device.constructor.name)) {
						m = g.get(device.constructor.name);
					} else if (g.has(device.type)) {
						m = g.get(device.type);
					} else if (ISYDevice.isNode(device)) {
						if (g.has(device.nodeDefId)) {
							m = g.get(device.nodeDefId);
						}

						if (!m) {
							for (var nodeDefId of ISYNode.getImplements(device)) {
								if (g.has(nodeDefId)) {
									device.logger(`Mapping found to ${Family[device.family]}.${nodeDefId}`, 'info');
									m = g.get(nodeDefId);
									g.set(device.nodeDefId, m);

									break;
								}
							}
						}
					}
					if (m !== null) this.cache[device.address] = m;
				}
			}
		}

		return m as any;
	}

	public static getMappingForBehavior<T extends ISYDevice.Any, const B extends ClusterBehavior>(device: T, behavior: B): ClusterMapping<B['cluster'], T> {
		//var m = MappingRegistry.getMapping(device);

		//return m[behavior.cluster.name];
		for (var m in MappingRegistry.getMapping(device).mapping) {
			if (behavior.cluster.name === m) return MappingRegistry.getMapping(device).mapping[m] as unknown as ClusterMapping<B['cluster'], T>;
		}
	}

	public static add<T extends 'Insteon', D extends keyof Devices[T], M extends MutableEndpoint>(mapping: { [x in D]?: DeviceToClusterMap<Devices[T][x], M> }): typeof MappingRegistry;
	public static add<T extends SupportedFamily, D extends keyof Devices[T], M extends MutableEndpoint>(mapping: { [x in D]?: DeviceToClusterMap<Devices[T][x], M> }): typeof MappingRegistry {
		MappingRegistry.register<T>(mapping as unknown as FamilyToClusterMap<T>);
		return MappingRegistry;
	}
	//@ts-ignore

	public static register<const T extends SupportedFamily>(map: FamilyToClusterMap<T>, family?: T): void {
		if ('Family' in map) {
			let regMap: Map<string, DeviceToClusterMap<ISYDevice.Any, MutableEndpoint>>;
			let Devices = DevicesNS[map.Family];
			if (!MappingRegistry.map.has(map.Family)) {
				MappingRegistry.map.set(map.Family, new Map());
			}

			regMap = MappingRegistry.map.get(map.Family);
			for (var key in map) {
				if (key !== 'Family' && key !== 'add') {
					let m = map[key] as DeviceToClusterMap<ISYNode.Factory<any, ISYNode<any, any, any, any>>, MutableEndpoint> & { toJSON: () => any };

					m = {
						deviceType: m.deviceType.with(BridgedDeviceBasicInformationBehavior, ISYBridgedDeviceBehavior, UserLabelServer, FixedLabelServer),
						mapping: m.mapping,

						toJSON() {
							return { deviceType: this.deviceType.name, mapping: this.mapping };
						}
					};
					if (m.mapping != undefined) {
						for (var key1 in m.mapping) {
							for (var key2 in m.mapping[key1].attributes) {
								let attribute = m.mapping[key1].attributes[key2];
								{
									let d = attribute;
									try {
										if (typeof d === 'string') {
											let [d1, d2] = d.split('.');
											if (d2) {
												m.mapping[key1].attributes[key2] = { driver: `${Devices[key]?.Nodes[d1].Drivers[d2]}`, node: d1 };
											} else {
												m.mapping[key1].attributes[key2] = { driver: Devices[key]?.Drivers[d1] };
											}
										} else if (hasPath(d)) {
											let [d1, d2] = d.driver.split('.');
											if (d2) {
												//@ts-ignore
												m.mapping[key1].attributes[key2].driver = `${Devices[key]?.Nodes[d1].Drivers[d2]}`;
												//@ts-ignore
												m.mapping[key1].attributes[key2].node = d1;
											} else {
												//@ts-ignore
												m.mapping[key1].attributes[key2].driver = Devices[key]?.Drivers[d1];
											}
										}
									} catch {
										console.log('Error', key, key1, key2, d);
									}
								}
							}
						}
					}
					console.log('Registering', JSON.stringify({ keys: [key, Devices[key]?.Class?.name, Devices[key]?.Class?.nodeDefId], mapping: m }, null, 2));
					regMap.set(key, m as any);
					regMap.set(Devices[key]?.Class?.name, m as any);

					if (ISYDevice.isNode(Devices[key])) {
						regMap.set(Devices[key]?.Class?.nodeDefId, m as any);
					}
				}
			}
		} /*else {
			let regMap: Map<string, DeviceToClusterMap<any, any>>;
			for (var key in map) {
				const keys = key.split('.');
				let x = DevicesNS[keys[0]][keys[1]] as typeof ISYNode<any, any, any, any>;
				if (!MappingRegistry.map.has(Family[x.family] as keyof typeof Family)) {
					MappingRegistry.map.set(Family[x.family] as keyof typeof Family, new Map());
				}
				//{family, key} = key.split(".")[0]

				regMap = MappingRegistry.map.get(Family[x.family] as keyof typeof Family);
				let m = map[key] as DeviceToClusterMap<ISYDevice.Any, MutableEndpoint>;
				let deviceType = m.deviceType;
				deviceType = deviceType.with(BridgedDeviceBasicInformationBehavior, ISYBridgedDeviceBehavior);

				m = { deviceType: deviceType, mapping: m.mapping };

				regMap.set(keys[1], m);
				regMap.set(x.name, m);
			}
		}
	}*/
		// #endregion Public Static Methods (3)
	}
}

// #endregion Classes (1)

// #region Variables (3)

/*interface SimplyEndpointMapping<T extends ISYNode<Family, any, any, any>, K extends MutableEndpoint> extends SimplifyDeep<ISYtoMatterMapping<T, K>> {}*/

/*
const map = {
	deviceType: DimmableLightDevice,
	mapping: {

		onOff: {
			attributes: {
			onOff: { driver: 'ST', converter: 'Percent.Boolean' },

			},
			commands: {
				onWithTimedOff: { command: 'DON' },

			}
		},
		levelControl: {
			attributes: {


			}
		}
	}
} as Mapping<Insteon.RelayLampNode, DimmableLightDevice>;
*/
// #endregion Variables (3)
