import { Utils } from "../common/Utils";
import { BaseComponent } from "../components/BaseComponent";

const __ = {
	private: Symbol('private'),
}

// Ignored attribute changes.
let ignoreChangingAttributes = ['_object', '_active', 'app'];

/**
 * @class MonitorDataComponent
 * 监控数据组件。
 * @example
 * // 订阅任何数据变化
 * let watcher1 = obj.monitorData.subscribe((ev) => {
 *   console.log(ev.data); // 变化的数据
 *   console.log(obj.monitorData); // 完整的数据
 * });
 *
 * // 订阅特定数据变化
 * let watcher2 = obj.monitorData.subscribe(["power", "freq"], (ev) => {
 *   console.log(ev.data["power"]);
 *   console.log(ev.data["freq"]);
 * });
 *
 * // 订阅单个数据变化
 * let watcher3 = obj.monitorData.subscribe("power", (ev) => {
 *   console.log(ev.data["power"]);
 * });
 *
 * // 获取监控属性
 * let power = obj.monitorData["power"];
 * let freq = obj.monitorData["freq"];
 *
 * // 设置监控属性
 * obj.monitorData.setAttributes({
 *   "power": 3200,
 *   "brand": "ant",
 *   "freq": 95
 * });
 *
 * // 取消特定订阅
 * obj.monitorData.unsubscribe(watcher1);
 *
 * // 取消所有订阅
 * obj.monitorData.unsubscribeAll();
 *
 * // 清空所有属性(不会取消订阅)
 * obj.monitorData.clearAttributes();
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class MonitorDataComponent extends BaseComponent {

	/**
	 * 监控数据组件,用于管理和订阅对象的属性变化。
	 */
	constructor() {
		super();

		this[__.private] = {};
		let _private = this[__.private];

		_private.anyPendingChanges = {};

		_private.pendingChangeCount = 0;
		_private.pendingChanges = {};
		_private.pendingSubscribers = [];

		_private.changedCount = 0;

		_private.monitorAttributesNames = [];
		_private.monitorAttributes = {};

		// subscription list
		_private._subscribers = new Map();

		// Callback when changing attributes.
		const changePropValue = (object, prop, value, receiver) => {
			// If it is an attribute that needs to ignore changes, do not proxy.
			if (ignoreChangingAttributes.includes(prop)) {
				return true;
			}

			// Store the original value for comparison
			const originalValue = object[prop];
			// Check if the value has actually changed
			const hasChanged = originalValue !== value;

			_private.monitorAttributes[prop] = value;

			if (_private.monitorAttributesNames.includes('*')) {
				if (!_private.pendingChangeCount) {
					// If it is a single attribute change and subscribed to a callback for any attribute change.
					if (hasChanged) {
						_private.anyPendingChanges = { [prop]: value };
					}
				}
				else {
					if (!hasChanged) {
						delete _private.anyPendingChanges[prop];
					}
					else {
						_private.anyPendingChanges[prop] = value;
					}
				}

				if (!_private.monitorAttributesNames.includes(prop)) {
					_private.monitorAttributesNames.push(prop);
				}
			}

			object[prop] = value;

			if (_private.monitorAttributesNames.includes(prop)) {
				// If there are multiple attribute changes.
				if (_private.pendingChangeCount) {
					// Only add to pendingChanges if the value has actually changed
					if (hasChanged) {
						_private.pendingChanges[prop] = value;
					}
					_private.changedCount++;
					if (_private.changedCount == _private.pendingChangeCount) {
						// Push all pending changes at once
						// Use anyPendingChanges for subscribers to all changes
						if (_private.monitorAttributesNames.includes('*')) {
							_private.app.monitorManager.push(_private.object, '*', _private.anyPendingChanges);
						} else {
							_private.app.monitorManager.push(_private.object, '*', _private.pendingChanges);
						}
						this._clearPendingChanges();
					}
				}
				else {
					// Single property change - only push if the value has actually changed
					if (hasChanged) {
						_private.app.monitorManager.push(_private.object, prop, value);
					}
				}
			}
		}

		// Proxy the set and deleteProperty interfaces of the class.
		let proxy = new Proxy(this, {
			set: function (object, prop, value, receiver) {
				changePropValue(object, prop, value, receiver);
				return true;
			},
			deleteProperty: function (object, prop) {
				changePropValue(object, prop);
				delete object[prop];
				return true;
			}
		});

		// Proxy monitorAttributes to this.
		Object.defineProperty(proxy, 'customFormatters', {
			enumerable: false,
			configurable: false,
			get: function () {
				return ['object', { object: _private.monitorAttributes }];
			}
		});

		return proxy;
	}

	/**
	 * 当组件被添加到对象时调用
	 * @param {object} object - 组件被添加到的对象
	 */
	onAdd(object) {
		// 不能调用父类接口,因为子类代理了属性操作
		// super.onAdd(object);

		let _private = this[__.private];
		_private.object = object;
		_private.app = object.isApp ? object : object.app;
	}

	/**
	 * 设置一个或多个属性
	 * @param {string|object} name - 属性名或包含多个属性的对象
	 * @param {*} [value] - 如果name是字符串,则为要设置的值
	 * @example
	 * // 设置单个属性
	 * obj.monitorData.setAttributes("power", 3200);
	 *
	 * // 设置多个属性
	 * obj.monitorData.setAttributes({
	 *   power: 3200,
	 *   freq: 95,
	 *   brand: "ant"
	 * });
	 *
	 * // 使用路径设置嵌套属性
	 * obj.monitorData.setAttributes("status/power", 3200);
	 * @public
	 */
	setAttributes(name, value) {
		let _private = this[__.private];

		if (Utils.isString(name)) {
			// Handle path-style attribute setting
			if (name.includes('/')) {
				const parts = name.split('/');
				let current = this;

				// Create nested objects along the path
				for (let i = 0; i < parts.length - 1; i++) {
					if (!current[parts[i]]) {
						current[parts[i]] = {};
					}
					current = current[parts[i]];
				}

				// Set the final value
				current[parts[parts.length - 1]] = value;
			}
			else {
				this[name] = value;
			}
		}
		else if (Utils.isObject(name)) {
			if (!_private.monitorAttributesNames.includes('*')) {
				const intersection = Object.keys(name).filter(value => _private.monitorAttributesNames.includes(value));
				_private.pendingChangeCount = intersection.length;
			}
			else {
				_private.pendingChangeCount = Object.keys(name).length;
			}

			_private.changedCount = 0;
			for (let prop in name) {
				// Support path-style keys in object
				if (prop.includes('/')) {
					this.setAttributes(prop, name[prop]);
				}
				else {
					this[prop] = name[prop];
				}
			}
		}
	}

	/**
	 * 订阅属性变化
	 * @param {string|string[]|Function} key - 要订阅的属性名(可以是数组),或者用于所有变化的回调函数
	 * @param {Function} [fn] - 如果key不是函数,则为回调函数
	 * @returns {object|object[]} 订阅对象或订阅对象数组
	 * @example
	 * // 订阅所有变化
	 * let watcher1 = obj.monitorData.subscribe((ev) => {
	 *   console.log(ev.data); // 变化的数据
	 * });
	 *
	 * // 订阅多个属性
	 * let watcher2 = obj.monitorData.subscribe(["power", "freq"], (ev) => {
	 *   console.log(ev.data.power);
	 *   console.log(ev.data.freq);
	 * });
	 *
	 * // 订阅单个属性
	 * let watcher3 = obj.monitorData.subscribe("power", (ev) => {
	 *   console.log(ev.data.power);
	 * });
	 * @public
	 */
	subscribe(key, fn) {
		let _private = this[__.private];

		// Monitor all data changes.
		if (Utils.isFunction(key)) {
			fn = key;
			key = '*';
		}
		// Monitoring partial data changes.
		else if (Utils.isArray(key)) {
			// Create a unique key for this array subscription
			const arrayKey = JSON.stringify(key.sort());

			// Add each property to the monitoring attribute array
			key.forEach(propKey => {
				if (!_private.monitorAttributesNames.includes(propKey)) {
					_private.monitorAttributesNames.push(propKey);
				}
			});

			// Store the array of properties with this subscriber
			const subscriber = this._addSubscriber(arrayKey, fn);
			subscriber.isArraySubscription = true;
			subscriber.properties = key;

			return subscriber;
		}

		// Add to monitoring attribute array.
		if (!_private.monitorAttributesNames.includes(key)) {
			_private.monitorAttributesNames.push(key);
		}

		return this._addSubscriber(key, fn);
	}

	_addSubscriber(key, fn) {
		let _private = this[__.private];

		if (Utils.isFunction(fn)) {
			let arr = _private._subscribers.get(key);
			if (arr) {
				arr.push(fn);
			}
			else {
				_private._subscribers.set(key, [fn])
			}

			return { key, listener: fn };
		}
		else {
			console.warn('Subscription callback should be a function.');
			return null;
		}
	}

	/**
	 * 取消订阅属性变化
	 * @param {object|object[]} subscriber - 由subscribe()返回的订阅对象或订阅对象数组
	 * @example
	 * // 取消单个订阅
	 * obj.monitorData.unsubscribe(watcher1);
	 *
	 * // 取消多个订阅
	 * obj.monitorData.unsubscribe([watcher1, watcher2]);
	 * @public
	 */
	unsubscribe(subscriber) {
		if (Utils.isArray(subscriber)) {
			subscriber.forEach(element =>
				this._unsubscribe(element.key, element.listener)
			);
		}
		else {
			this._unsubscribe(subscriber.key, subscriber.listener)
		}
	}

	_unsubscribe(key, fn) {
		let _private = this[__.private];
		let fns = _private._subscribers.get(key);
		if (fns && fns.length > 0) {
			for (let i = fns.length - 1; i >= 0; i--) {
				if (fns[i] == fn) {
					fns.splice(i, 1);
				}
			}
		}
	}

	/**
	 * 取消所有属性变化的订阅
	 * @example
	 * // 取消所有订阅
	 * obj.monitorData.unsubscribeAll();
	 * @public
	 */
	unsubscribeAll() {
		let _private = this[__.private];
		_private._subscribers.clear();
	}

	/**
	 * 清除所有被监控的属性
	 * @example
	 * // 清除所有监控属性
	 * obj.monitorData.clearAttributes();
	 * @public
	 */
	clearAttributes() {
		let _private = this[__.private];
		for (let prop in _private.monitorAttributes) {
			delete this[prop];
		}
		_private.monitorAttributes = {};
	}

	_publish(prop, value) {
		let _private = this[__.private];

		// Track which callbacks have been called to prevent duplicates
		const calledCallbacks = new Set();

		// If prop is '*', it means we're publishing all changes at once
		if (prop === '*' && typeof value === 'object') {
			this._publishMultipleChanges(value, calledCallbacks);
		} else {
			this._publishSingleChange(prop, value, calledCallbacks);
		}

		this._clearPendingChanges();
	}

	/**
	 * 发布多个属性变化事件
	 * @param {object} changes - 变化的属性对象
	 * @param {Set} calledCallbacks - 已调用的回调函数集合
	 * @private
	 */
	_publishMultipleChanges(changes, calledCallbacks) {
		let _private = this[__.private];

		// 前面已经过滤了未变化的属性,这里直接使用
		const changedProps = Object.keys(changes).filter(key => key !== '*');

		// Only proceed if there are actually changed properties
		if (changedProps.length > 0) {
			// 处理订阅所有变化的回调
			this._notifyAllChangesSubscribers(changes, calledCallbacks);

			// 处理数组订阅
			this._notifyArraySubscribers(changedProps, changes, calledCallbacks);

			// 处理单属性订阅
			this._notifyPropertySubscribers(changedProps, changes, calledCallbacks);
		}
	}

	/**
	 * 发布单个属性变化事件
	 * @param {string} prop - 变化的属性名
	 * @param {*} value - 变化的属性值
	 * @param {Set} calledCallbacks - 已调用的回调函数集合
	 * @private
	 */
	_publishSingleChange(prop, value, calledCallbacks) {
		let _private = this[__.private];

		// 处理订阅所有变化的回调
		this._notifyAllChangesSubscribers(_private.anyPendingChanges, calledCallbacks);

		// 处理数组订阅
		this._notifyArraySubscribersForSingleProp(prop, value, calledCallbacks);

		// 处理多属性变化
		this._notifyMultiPropSubscribers(calledCallbacks);

		// 处理单属性订阅
		this._notifyPropertySubscriber(prop, value, calledCallbacks);
	}

	_clearPendingChanges() {
		let _private = this[__.private];

		// Clear pending attribute changes.
		_private.anyPendingChanges = {};

		_private.pendingChanges = {};
		_private.pendingChangeCount = 0;
		_private.changedCount = 0;
	}

	_findCommonValues(map, keys) {
		if (!Array.isArray(keys) || keys.length === 0) {
			return [];
		}

		// Filter out '*' from keys if present
		const filteredKeys = keys.filter(key => key !== '*');

		// If all keys were '*', return an empty array
		if (filteredKeys.length === 0) {
			return [];
		}

		// Find the callback corresponding to the monitored attribute.
		const valueArrays = filteredKeys.map(
			key => map.get(key)
		).filter(array => array && array.length > 0); // Filter out undefined or empty arrays

		// If no valid arrays found, return empty array
		if (valueArrays.length === 0) {
			return [];
		}

		// Using the reduce () method to find the same value.
		const commonValues = valueArrays.reduce((intersection, currentArray) => {
			if (!intersection || intersection.length === 0) {
				return currentArray;
			}
			return intersection.filter(value => currentArray.includes(value));
		}, []);

		// return commonValues;
		return [...new Set(commonValues)];
	}

	_notifyAllChangesSubscribers(changes, calledCallbacks) {
		let _private = this[__.private];

		// For subscribers to all changes
		let anyFns = _private._subscribers.get('*');
		if (anyFns && anyFns.length > 0) {
			this._notifySubscribers(anyFns, changes, calledCallbacks);
		}
	}

	_notifyArraySubscribers(changedProps, changes, calledCallbacks) {
		let _private = this[__.private];

		// For array subscriptions, check if any of the properties in the array have changed
		_private._subscribers.forEach((fns, key) => {
			// Skip if this is not a JSON string (not an array subscription)
			if (!key.startsWith('[') || !key.endsWith(']')) return;

			try {
				// Parse the array key
				const properties = JSON.parse(key);

				// Check if any of the properties in this subscription have changed
				const relevantChanges = {};
				let hasRelevantChanges = false;

				properties.forEach(prop => {
					if (changedProps.includes(prop)) {
						relevantChanges[prop] = changes[prop];
						hasRelevantChanges = true;
					}
				});

				// If there are relevant changes, notify subscribers
				if (hasRelevantChanges) {
					this._notifySubscribers(fns, relevantChanges, calledCallbacks);
				}
			} catch (e) {
				// Ignore parsing errors
			}
		});
	}

	_notifyPropertySubscribers(changedProps, changes, calledCallbacks) {
		let _private = this[__.private];

		// For subscribers to specific properties
		changedProps.forEach(changedProp => {
			let propFns = _private._subscribers.get(changedProp);
			if (propFns && propFns.length > 0) {
				this._notifySubscribers(propFns, { [changedProp]: changes[changedProp] }, calledCallbacks);
			}
		});
	}

	_notifyArraySubscribersForSingleProp(prop, value, calledCallbacks) {
		let _private = this[__.private];

		// Check array subscriptions that include this property
		_private._subscribers.forEach((fns, key) => {
			// Skip if this is not a JSON string (not an array subscription)
			if (!key.startsWith('[') || !key.endsWith(']')) return;

			try {
				// Parse the array key
				const properties = JSON.parse(key);

				// Check if this property is in the subscription
				if (properties.includes(prop)) {
					this._notifySubscribers(fns, { [prop]: value }, calledCallbacks);
				}
			} catch (e) {
				// Ignore parsing errors
			}
		});
	}

	_notifyMultiPropSubscribers(calledCallbacks) {
		let _private = this[__.private];

		// If the number of pending changes is greater than 0, only messages that are subscribed to together with these attributes will be published.
		if (_private.pendingChangeCount > 0 && Object.keys(_private.pendingChanges).length > 0) {
			let commonFns = this._findCommonValues(_private._subscribers, Object.keys(_private.pendingChanges));
			if (commonFns.length > 0) {
				this._notifySubscribers(commonFns, _private.pendingChanges, calledCallbacks);
			}
		}
	}

	_notifyPropertySubscriber(prop, value, calledCallbacks) {
		let _private = this[__.private];

		// Get the function by prop and trigger these functions.
		let fns = _private._subscribers.get(prop);
		if (fns && fns.length > 0) {
			this._notifySubscribers(fns, { [prop]: value }, calledCallbacks);
		}
	}

	/**
	 * 通知订阅者
	 * @param {Array} subscribers - 订阅者数组
	 * @param {object} data - 要发送的数据
	 * @param {Set} calledCallbacks - 已调用的回调函数集合
	 * @private
	 */
	_notifySubscribers(subscribers, data, calledCallbacks) {
		subscribers.forEach(fn => {
			// Skip if this callback has already been called
			if (calledCallbacks.has(fn)) return;

			// Mark this callback as called
			calledCallbacks.add(fn);

			fn({ data });
		});
	}

}

export { MonitorDataComponent }