import { DoorLock, FanControl } from '@matter/main/clusters';
import { UnitOfMeasure } from './Definitions/Global/UOM.js';
import { FanLevel, RampRate } from './Definitions/Insteon/index.js';

import { Paths } from 'type-fest';
import { Insteon } from './ISY.js';
import { type PathsWithLimit, type StringKeys } from './Utils.js';

let BooleanPercentage: Converter<boolean, number>;
let NullConverter: Converter<any, any>;
{
	to: (value: any) => value;
	from: (value: any) => value;
}

const StandardConverters = {
	Boolean: {
		LevelFrom0To255: {
			to: (value: boolean) => (value ? 255 : 0),
			from: (value: number) => value > 0,
		},
		Percent: {
			to: (value: boolean) => (value ? 100 : 0),
			from: (value: number) => value > 0,
		},
	},
	Fahrenheit: {
		Celsius: {
			to: (value: number) => (value - 32) / 1.8,
			from: (value: number) => value * 1.8 + 32,
		},
		LevelFrom0To255: {
			to: (value: number) => Math.round(value / 2.55),
			from: (value: number) => Math.round(value * 2.55),
		},
	},
	Celsius: {
		LevelFrom0To255: {
			to: (value: number) => Math.round(value / 2.55),
			from: (value: number) => Math.round(value * 2.55),
		},
	},
	LevelFrom0To255: {
		Percent: {
			to: (value: number): number => {
				if (value === 0) return 0;
				if (value === 255) return 100;
				return Math.round((value * 100) / 255);
			},
			from: (value: number): number => {
				if (value === 0) return 0;
				if (value === 100) return 255;
				return Math.round((value * 255) / 100);
			},
		},
	},
};

//StandardConverters.Percent.LevelFrom0To255 = invert(StandardConverters.LevelFrom0To255.Percent);
//StandardConverters.LevelFrom0To255.Boolean = invert(StandardConverters.Boolean.LevelFrom0To255);

export const StdConverterRegistry = new Map<UnitOfMeasure | string, Map<UnitOfMeasure | string, Converter<any, any>>>();

export const ConverterRegistry = new Map<string, Converter<any, any>>();

function registerConverters() {
	for (const from in StandardConverters) {
		for (const to in StandardConverters[from]) {
			registerConverter(from, to, StandardConverters[from][to] as Converter<any, any>);
			registerConverter(to, from, invert(StandardConverters[from][to]) as Converter<any, any>);
		}
	}
	for (const from in Converter.Matter) {
		for (const to in Converter.Matter[from]) {
			registerConverter(from, to, Converter.Matter[from][to] as Converter<any, any>);
			registerConverter(to, from, invert(Converter.Matter[from][to]) as Converter<any, any>);
		}
	}
}

function registerConverter(
	from:
		| keyof typeof StandardConverters
		| keyof typeof Converter.Matter
		| Paths<typeof StandardConverters>
		| Paths<typeof Converter.Matter>
		| string,
	to: keyof typeof StandardConverters | keyof typeof Converter.Matter | string,
	converter: Converter<any, any>
) {
	if (!StdConverterRegistry.has(from)) {
		StdConverterRegistry.set(from, new Map());
	}

	StdConverterRegistry.get(from).set(to, converter);
	let key = from + '.' + to;
	if (!ConverterRegistry.has(key)) {
		ConverterRegistry.set(key, converter);
	}
}

export namespace Converter {
	export const Standard: typeof StandardConverters = StandardConverters;
	//TODO: Migrate to isy matter
	//TODO: Simplify

	export const Matter = {
		LevelFrom0To255: {
			LightingLevel: {
				from: (value: number) => (value === 1 ? 0 : value === 254 ? 255 : value),
				to: (value: number) => (value === 0 ? 1 : value === 255 ? 254 : value),
			},
		},
		Percent: {
			LightingLevel: {
				from: (value: number) => (value === 1 ? 0 : value === 254 ? 100 : Math.round((value / 254) * 100)),
				to: (value: number) => (value === 0 ? 1 : value === 100 ? 254 : Math.round((value / 100) * 254)),
			},
			InverseBoolean: {
				from: (value: boolean) => (value ? 0 : 100),
				to: (value: number) => (value > 0 ? false : true),
			},
		},

		ZWaveLock: {
			LockState: {
				from: (value: DoorLock.LockState) => (value == DoorLock.LockState.Locked ? 101 : 0),
				to: (value: Insteon.Lock) => (value == 0 ? DoorLock.LockState.Unlocked : DoorLock.LockState.Locked),
			},
		},

		InsteonLock: {
			LockState: {
				from: (value: DoorLock.LockState) =>
					value == DoorLock.LockState.Locked ? Insteon.Lock.Locked : Insteon.Lock.Unlocked,
				to: (value: Insteon.Lock) =>
					value == Insteon.Lock.Locked ? DoorLock.LockState.Locked : DoorLock.LockState.Unlocked,
			},
		},
		Boolean: {
			LightingLevel: {
				from: (value: number) => value > 0,
				to: (value: boolean) => (value ? 254 : 0),
			},
		},
		RampRate: {
			Deciseconds: {
				from: (value: number) => RampRate.from.seconds(value / 10),
				to: (value: RampRate) => RampRate.to.seconds(value) * 10,
			},
			Seconds: {
				from: (value: number) => RampRate.from.seconds(value),
				to: (value: RampRate) => RampRate.to.seconds(value),
			},
		},

		FanLevel: {
			FanMode: {
				from: (value: FanControl.FanMode) => {
					switch (value) {
						case FanControl.FanMode.Low:
							return FanLevel.Low;
						case FanControl.FanMode.Medium:
							return FanLevel.Medium;
						case FanControl.FanMode.High:
							return FanLevel.High;
						case FanControl.FanMode.Off:
							return FanLevel.Off;
						default:
							throw new Error('Invalid Fan Mode: ' + FanControl.FanMode[value]);
					}
				},
				to: (value: FanLevel) => {
					switch (value) {
						case FanLevel.Low:
							return FanControl.FanMode.Low;
						case FanLevel.Medium:
							return FanControl.FanMode.Medium;
						case FanLevel.High:
							return FanControl.FanMode.High;
						case FanLevel.Off:
							return FanControl.FanMode.Off;
					}
				},
			},
		},
	};

	export type ConverterTypes = `${StringKeys<typeof StandardConverters>}`;

	type StandardConverters = Paths<
		typeof StandardConverters,
		{ maxRecursionDepth: 1; bracketNotation: false; depth: 1 }
	>;

	type Invert<K extends string> = K extends `${infer T}.${infer U}` ? `${U}.${T}` | K : never;
	//export type StandardConvertersList = PathsWithLimit<typeof StandardConverters, 1>;

	export type MatterISYConvertibleTypes = `${StringKeys<(typeof Matter)[`${keyof typeof Matter}`]>}`;

	export type ISYMatterConvertibleTypes = `${StringKeys<typeof Matter>}`;

	export type MatterConverters = Paths<
		typeof Matter,
		{ maxRecursionDepth: 1; bracketNotation: false; leavesOnly: true }
	>;

	export type KnownConverters =
		| StandardConverters
		| MatterConverters
		| Invert<StandardConverters>
		| Invert<MatterConverters>;
	const cache: { [x: string]: Converter<any, any> } = {};
	export function get(label: KnownConverters): Converter<any, any>;
	export function get(from: UnitOfMeasure, to: UnitOfMeasure): Converter<any, any>;
	export function get(from: ConverterTypes, to: ConverterTypes);
	export function get(from: UnitOfMeasure, to: UnitOfMeasure);
	export function get(from: MatterISYConvertibleTypes, to: ISYMatterConvertibleTypes);
	export function get(to: ISYMatterConvertibleTypes, from: MatterISYConvertibleTypes);
	export function get(
		from: UnitOfMeasure | `${keyof typeof UnitOfMeasure}` | string,
		to?: UnitOfMeasure | `${keyof typeof UnitOfMeasure}` | string
	): Converter<any, any> {
		if (to === undefined) {
			return ConverterRegistry.get(from as string) ?? NullConverter;
		}
		if (cache[`${from}.${to}`]) {
			return cache[`${from}.${to}`];
		} else if (cache[`${to}.${from}`]) {
			cache[`${from}.${to}`] = invert(cache[`${to}.${from}`]);
			return cache[`${from}.${to}`];
		}
		let isString = typeof from === 'string';
		let fuom = isString ? from : UnitOfMeasure[from];
		if (to) {
			let tuom = typeof to === 'string' ? to : UnitOfMeasure[to];

			if (StdConverterRegistry.has(fuom)) {
				if (StdConverterRegistry.get(fuom).has(tuom)) {
					return StdConverterRegistry.get(fuom).get(tuom);
				}
			}
		}
		return NullConverter;
	}

	export function convert<F, T>(from: UnitOfMeasure, to: UnitOfMeasure, value: F): T {
		const converter = get(from, to);
		if (converter) {
			return converter.to(value);
		}
		return null;
	}

	export function register<F, T>(from: UnitOfMeasure, to: UnitOfMeasure, converter: Converter<F, T>) {
		registerConverter(UnitOfMeasure[from], UnitOfMeasure[to], converter);
	}
	export function registerAll(converters: Record<string, Record<string, Converter<any, any>>>) {
		for (const from in converters) {
			for (const to in converters[from]) {
				registerConverter(from, to, converters[from][to]);
			}
		}
	}
}

registerConverters();
export interface Converter<F, T> {
	// #region Properties (2)
	from: (value: F) => T;
	to: (value: T) => F;
} //onst D: d = 'ST';
//type DriverLabel = Values<IdentityOf<DriverType>>;

export function invert<F, T>(converter: Converter<F, T>): Converter<T, F> {
	return {
		from: converter.to,
		to: converter.from,
	};
}
