import type { Constructor } from 'type-fest';
import type { Command } from './Definitions/Global/Commands.js';
import type { Driver } from './Definitions/Global/Drivers.js';
import type { Event } from './Definitions/Global/Events.js';
import type { Feature } from './Definitions/Global/Features.js';
import type { Category, Family } from './Definitions/index.js';
import type { CompositeDevice } from './Devices/CompositeDevice.js';
import type { DynamicNode } from './Devices/DynamicNode.js';
import { ISYDeviceNode } from './Devices/ISYDeviceNode.js';
import type { ISYNode } from './ISYNode.js';
import type { ISYScene } from './Scenes/ISYScene.js';
import type { Factory as BaseFactory } from './Utils.js';

export interface Queryable {
	query(): Promise<any>;
}

/**
 * Represents an ISY device with specific properties and methods.
 *
 * @template T - The family type of the device.
 * @template D - The type of the drivers associated with the device.
 * @template C - The type of the commands associated with the device.
 * @template E - The type of the events associated with the device.
 *
 * @extends ISYDeviceInfo
 *
 * @property {string} address - The address of the device.
 * @property {T extends Family.Insteon ? Category.Insteon : Category.Home.Category} category - The category of the device based on its family.
 * @property {C} commands - The commands associated with the device.
 * @property {any} deviceClass - The class of the device.
 * @property {D} drivers - The drivers associated with the device.
 * @property {E} events - The events associated with the device.
 * @property {boolean} enabled - Indicates whether the device is enabled.
 * @property {T} family - The family of the device.
 * @property {boolean} hidden - Indicates whether the device is hidden.
 * @property {boolean} isDimmable - Indicates whether the device is dimmable.
 * @property {string} label - The label of the device.
 * @property {string} model - The model of the device.
 * @property {string} modelNumber - The model number of the device.
 * @property {any} name - The name of the device.
 * @property {any} parentAddress - The parent address of the device.
 * @property {ISYScene[]} scenes - The scenes associated with the device.
 * @property {number} subCategory - The sub-category of the device.
 * @property {any} type - The type of the device.
 * @property {string} typeCode - The type code of the device.
 * @property {string} version - The version of the device.
 * @property {string} location - The location of the device.
 * @property {string} folder - The folder to which the device belongs.
 */
export interface ISYDevice<T extends Family, D, C, E> extends ISYDeviceInfo {
	// #region Properties (22)
	address: string;
	category: T extends Family.Insteon ? Category.Insteon : Category.Home.Category;
	commands: C;
	deviceClass: any;
	drivers: D;
	events: E;
	enabled: boolean;
	family: T;
	hidden: boolean;
	label: string;
	model: string;
	modelNumber: string;
	name: any;
	parentAddress: any;
	scenes: ISYScene[];
	subCategory: number;
	type: any;
	typeCode: string;
	version: string;
	features: Feature;
	location: string;
	folder?: string; // Optional folder property to group devices
	initialized: boolean;
	refreshState(): Promise<any>;

	// #endregion Properties (22)

	// #region Public Methods (18)

	// #endregion Public Methods (18)
}

export namespace ISYDevice {
	export function isDevice<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	>(
		device: ISYNode<T, D, C, E>
	): device is ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, Event.ForAll<E, D, C>> & ISYNode<T, D, C, E> {
		return device instanceof ISYDeviceNode;
	}

	export type Factory<F extends Family, D, C, E, T extends ISYDevice<F, D, C, E>> =
		T extends ISYNode<F, any, any, any>
			? ISYNode.Factory<F, T>
			: T extends CompositeDevice<F, infer N>
				? CompositeDevice.Factory<F, N, T>
				: never;

	type Node<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	> = ISYNode<T, D, C, E> & ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, Event.ForAll<E, D, C>>;

	export function isNode<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	>(
		factory: BaseFactory<ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, any>>
	): factory is ISYNode.Factory<T, Node<T, D, C, E>>;
	export function isNode<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	>(device: ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, any>): device is Node<T, D, C, E>;
	export function isNode<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	>(
		factoryOrDevice:
			| ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, any>
			| BaseFactory<ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, any>>
	): factoryOrDevice is Node<T, D, C, E> | ISYNode.Factory<T, Node<T, D, C, E>> {
		if ('Class' in factoryOrDevice) {
			return 'Drivers' in factoryOrDevice;
		}
		return factoryOrDevice instanceof ISYDeviceNode;
	}

	export function isDynamic(node: ISYDevice.Any): node is DynamicNode<any, any, any, any> {
		return 'nodeTypeId' in node;
	}
	export function isNodeFactory<
		T extends Family,
		D extends ISYNode.DriverSignatures,
		C extends ISYNode.CommandSignatures,
		E extends ISYNode.EventSignatures,
	>(
		factory: BaseFactory<ISYDevice<T, Driver.ForAll<D>, Command.ForAll<C>, any>>
	): factory is ISYNode.Factory<T, Node<T, D, C, E>> {
		return 'Drivers' in factory;
	}

	export function isComposite<
		T extends Family,
		D extends Record<string, object>,
		C extends Record<string, object>,
		E extends Record<string, object>,
	>(
		factory: BaseFactory<ISYDevice<T, D, C, E>>
	): factory is CompositeDevice.Factory<T, { [x in keyof D]: ISYNode.Factory<T, any> }, any>;
	export function isComposite<
		T extends Family,
		D extends Record<string, object>,
		C extends Record<string, object>,
		E extends Record<string, object>,
	>(
		device: ISYDevice<T, D, C, E>
	): device is ISYDevice<T, D, C, E> & CompositeDevice<T, { [x in keyof D]: ISYNode.Factory<T, any> }>;
	export function isComposite<
		T extends Family = Family,
		D extends Record<string, object> = {},
		C extends Record<string, object> = {},
		E extends Record<string, object> = {},
	>(
		deviceOrFactory: ISYDevice<T, D, C, E> | BaseFactory<ISYDevice<T, D, C, E>>
	): deviceOrFactory is
		| (ISYDevice<T, D, C, E> & CompositeDevice<T, { [x in keyof D]: ISYNode.Factory<T, any> }>)
		| CompositeDevice.Factory<T, { [x in keyof D]: ISYNode.Factory<T, any> }, any> {
		if ('Class' in deviceOrFactory) return 'Nodes' in deviceOrFactory;
		return 'root' in deviceOrFactory;
	}

	export type DriverNamesOf<T> = T extends { Class: Constructor<CompositeDevice<any, any>> }
		? CompositeDevice.DriverNamesOf<InstanceType<T['Class']>>
		: ISYNode.DriverNamesOf<T>;

	export type ChildrenOf<T> = T extends CompositeDevice.Factory<any, infer N, any> ? N : never;

	export type CommandNamesOf<T> = T extends BaseFactory<CompositeDevice<any, any, any>> | CompositeDevice<any, any, any>
		? CompositeDevice.CommandNamesOf<T>
		: ISYNode.CommandNamesOf<T>;

	export type Any = ISYDevice<any, any, any, any>;

	export type EventNamesOf<T extends ISYDevice.Any | BaseFactory<ISYDevice.Any>> =
		InstanceTypeOf<T> extends CompositeDevice<any, any, any>
			? CompositeDevice.EventNamesOf<InstanceTypeOf<T>>
			: T extends ISYNode
				? ISYNode.EventNamesOf<T>
				: never;

	export function isQueryable(device: ISYDevice.Any): device is Queryable & ISYDevice.Any {
		if (device?.commands && typeof device.commands == 'object') return 'QUERY' in device.commands;
		return false;
	}

	export type InstanceTypeOf<T> = T extends ISYDevice.Any
		? T
		: T extends BaseFactory<ISYDevice.Any> & { Node: Constructor<ISYDevice.Any> }
			? InstanceType<T['Node']>
			: T extends { Device: Constructor<ISYDevice.Any> }
				? InstanceType<T['Device']>
				: never;
}

export function isDevice<
	T extends Family,
	D extends ISYNode.DriverSignatures,
	C extends ISYNode.CommandSignatures,
	E extends ISYNode.EventSignatures,
>(device: ISYNode<T, D, C, E>): device is ISYDevice<T, D, C, E> & ISYNode<T, D, C, E> {
	return device instanceof ISYDeviceNode;
}

export function isDeviceClass<
	T extends Family,
	D extends ISYNode.DriverSignatures,
	C extends ISYNode.CommandSignatures,
	E extends ISYNode.EventSignatures,
>(device: typeof ISYNode<T, D, C, E>): device is (new (...args) => ISYDevice<T, D, C, E>) & typeof ISYNode<T, D, C, E> {
	return device.prototype instanceof ISYDeviceNode;
}

export interface ISYDeviceInfo {
	type: string;
	deviceClass: any;

	productName: string;

	productId: string | number;

	model: string;

	modelName: string;

	modelNumber: string;

	version: string;

	category: Category.Insteon | Category.Home.Category;

	subCategory: number;

	manufacturer: string;
}
