const deepEqual = (a: any, b: any) => {
	if (a === b) return true;
   
	if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
   
	let keysA = Object.keys(a), keysB = Object.keys(b);
   
	if (keysA.length !== keysB.length) return false;
   
	for (let key of keysA) {
		if (!keysB.includes(key)) return false;
	
		if (typeof a[key] === 'function' || typeof b[key] === 'function') {
			if (a[key].toString() !== b[key].toString()) return false;
		} else {
			if (!deepEqual(a[key], b[key])) return false;
		}
	}
   
	return true;
}

export type Watched<T> = {
	value: T;
	subscribe: (f: (newVal: T, oldVal: T) => void) => void;
	unsubscribe: (f: (newVal: T, oldVal: T) => void) => void;
	locked: boolean;
}

export const watch = <T extends Record<any,any> | any[]>(target: T): Watched<T> => {
	let listeners: Array<(newVal: T, oldVal: T) => void> = [];
	let locked = false;
	const proxy = new Proxy(target, {
		get(t: any, prop) {
			return t[prop as any];
		},
		set(t: any, prop, newValue) {
			if (locked) {
				return false;
			}
			const prev = structuredClone(t);
			t[prop as any] = newValue;
			if (deepEqual(prev, t)) {
				listeners.forEach(listener => listener(t, prev));
			}
			return true;
		}
	});
	return {
		get value() {
			return target;
		},
		subscribe(f) {
			listeners.push(f)
		},
		unsubscribe(f) {
			listeners = listeners.filter(l => l !== f);
		},
		get locked() {
			return locked;
		},
		set locked(newVal: boolean) {
			locked = newVal;
		}
	}
}

