type _get_callback_<T, prop extends keyof T> = (proxy: T, value: T[prop])=>T[prop];
type _set_callback_<T, prop extends keyof T> = (proxy: T, old: T[prop], value: T[prop])=>T[prop];

const __Global__ = window ?? globalThis;

/** 代理拦截数据 */
export type DataBlocker<T> = {
	get?: {[prop in keyof T]?: _get_callback_<T, prop>}&{ignore?: Array<mtec.OmitKeys<T, Function>>},
	set?: {[prop in keyof T]?: _set_callback_<T, prop>}&{ignore?: Array<mtec.OmitKeys<T, Function>>},
	afterSet?: (prop: string|symbol, old: any, value: any, hdl: PxiHandle<T>)=>void;
}

export class PxiHandle<T extends Object|Array<any>> implements ProxyHandler<T>{

	constructor(private tgt: T, private name: string, private prt: PxiHandle<any>, private blocker?:DataBlocker<T>){
		if(blocker) this.in(blocker);
		this.revocable = Proxy.revocable(tgt, this);
		this.old = (Array.isArray(tgt)?[]:{}) as T;
		this.backup();
	}

	private getGetCall<prop extends keyof T>(key: prop): _get_callback_<T, prop>{
		let call: _get_callback_<T, prop>;
		if(this.blocker && this.blocker.get && Reflect.has(this.blocker.get, key)) call = Reflect.get(this.blocker.get, key) as _get_callback_<T, prop>;
		return call;
	}

	private getSetCall<prop extends keyof T>(key: prop): _set_callback_<T, prop>{
		let call: _set_callback_<T, prop>;
		if(this.blocker && this.blocker.set && Reflect.has(this.blocker.set, key)) call = Reflect.get(this.blocker.set, key) as _set_callback_<T, prop>;
		return call;
	}

	private backup(key?: any){
		let value: any;
		if(key===undefined){
			value = mtec.cloneData(this.target);
			this.old = value;
		}else{
			value = Reflect.get(this.target, key);
			value = mtec.cloneData(value);
			Reflect.set(this.old, key, value);
		}

		return value;
	}

	valueOf(){
		let temp: any;
		if(Array.isArray(this.tgt)){
			temp = [];
			let proxy = this.proxy as Array<any>;
			for(let i = 0; i < proxy.length; i++) temp.push(Reflect.get(proxy, i));
		}else{
			temp = {};
			let proxy = this.proxy as Object;
			Reflect.ownKeys(proxy).forEach(key=>Reflect.set(temp, key, Reflect.get(proxy, key)));
		}

		return temp as T;
	}

	private async publish(key: string|symbol, old: any, value: any){
		if(key!==undefined){
			let path = this.path + '.' + String(key);
			DataProxy.publish(path, old, value);
		}

		if(this.parent) this.parent.publish(this.name, this.old, this.target);
	}

	public in(blocker: DataBlocker<T>){
		if(this.blocker === undefined) this.blocker = {};
		let props: any[] = [];
		if(Reflect.has(blocker, 'get')){
			if(this.blocker.get === undefined) this.blocker.get = {};
			Reflect.ownKeys(blocker.get).forEach(p=>{
				Reflect.set(this.blocker.get, p, Reflect.get(blocker.get, p));
				if(!props.includes(p)) props.push(p);
			});
		}
		if(Reflect.has(blocker, 'set')){
			if(this.blocker.set === undefined) this.blocker.set = {};
			Reflect.ownKeys(blocker.set).forEach(p=>{
				Reflect.set(this.blocker.set, p, Reflect.get(blocker.set, p));
				if(!props.includes(p)) props.push(p);
			});
		}
		Reflect.ownKeys(blocker).filter(k=>!['get', 'set'].includes(k as any))
		.forEach(p=>Reflect.has(blocker, p)?Reflect.set(this.blocker, p, Reflect.get(blocker, p)):void 0);

		props.filter(p=>{
			let value = Reflect.get(this.tgt, p);
			return typeof value === 'object' && value !== null;
		}).forEach(p=>{
			let value = Reflect.get(this.tgt, p);
			if(!this.children.has(p) || this.children.get(p).target!==value){
				let handle = DataProxy.proxy(value, String(p), this).handle;
				this.children.set(p, handle);
			}
		});
	}

	/** 销毁所有代理 */
	public revoke(){
		this.children.forEach(h=>h.revoke());
		this.children.clear();
		DataProxy.revoke(this);
		this.revocable.revoke();
	}

	get(target: T, prop: string|symbol, receiver: any): T[keyof T]{
		let value: any;
		if(this.blocker?.get?.ignore?.includes(prop as any)) value = Reflect.get(target, prop);
		else{
			value = this.children.has(prop) ? this.children.get(prop).proxy : Reflect.get(target, prop);

			if(typeof value === 'function') value = Reflect.apply(Function.bind, value, [receiver]);
			else{
				let call = this.getGetCall(prop as any);
				value = call ? call(receiver, value) : value;
			}
		}

		if(typeof value === 'object' && value !== null && !this.children.has(prop)){
			let handle = DataProxy.proxy(value, String(prop), this).handle;
			this.children.set(prop, handle);
		}

		return value;
	}

	set(target: T, prop: string|symbol, value: any, receiver: any): boolean{
		if(this.blocker && this.blocker.set && this.blocker.set.ignore && this.blocker.set.ignore.includes(prop as any)){
			Reflect.set(target, prop, undefined);
			return true;
		}

		let old = this.backup(prop);

		let call = this.getSetCall(prop as any);
		if(typeof value === 'object' && value !== null && !this.children.has(prop)){
			let handle = DataProxy.proxy(value, String(prop), this).handle;
			this.children.set(prop, handle);
		}

		let new_value = call ? call(receiver, old, value) : value;
		if(this.children.has(prop) && this.children.get(prop).target !== new_value){
			let hdl = this.children.get(prop);
			if(!new_value){
				hdl.revoke();
				this.children.delete(prop);
			}else if(Array.isArray(new_value)){
				hdl.proxy.splice(0, hdl.proxy.length);
				hdl.proxy.push(...new_value);
			}else Object.assign(hdl.proxy, new_value);
		}

		Reflect.set(target, prop, this.children.has(prop) ? this.children.get(prop).valueOf() : new_value);

		if(this.blocker?.afterSet) this.blocker.afterSet(prop, old, new_value, this);

		this.publish(prop, old, new_value);

		return true;
	}

	private revocable: ReturnType<typeof Proxy.revocable>;
	private old: T;
	/** 本级代理 */
	public get proxy(){
		return this.revocable.proxy as T;
	}
	/** 代理对象 */
	public get target(){
		return this.tgt;
	}
	/** 父级handle */
	public get parent(){
		return this.prt;
	}
	/** 子级handle */
	private children = new Map<any, PxiHandle<any>>();
	/** 当前handle的路径 */
	public get path(): string{
		return this.parent ? this.parent.path+'.'+this.name : this.name;
	}
}

export class DataProxy{

    /** 代理句柄映射 */
	private static id_map: Map<any, string> = new Map();
	private static record: Map<string, [any, any, PxiHandle<any>]> = new Map();

    /**
     * 生成代理对象
     * @param ins 数据实例
     * @param name 数据名称
     */
    public static proxy<I extends Object>(ins: I, name?: string, parent: PxiHandle<any> = null){
		let proxy: I, handle: PxiHandle<I>;
		if(DataProxy.id_map.has(ins)){
			let id = DataProxy.id_map.get(ins);
			let [target, pxi, handle] = DataProxy.record.get(id);
			proxy = pxi, handle = handle;
		}else{
			let id = mtec.string.randomToken(8, 36, token=>!DataProxy.record.has(token));

			handle = new PxiHandle(ins, name??id, parent);
			proxy = handle.proxy;

			[ins, handle, handle.proxy].forEach(i=>DataProxy.id_map.set(i, id));
			DataProxy.record.set(id, [ins, handle.proxy, handle]);
		}

        return {proxy, handle};
    }

	/** 是否已注册代理 */
	public static hasProxy(ins: any){
		return DataProxy.id_map.has(ins);
	}

	/**
	 * 初始化一个代理
	 * @param target 代理对象
	 * @param save 是否需要本地存储
	 * @param init 初始化句柄的回调
	 * @returns
	 */
	public static initProxy<D>(name: string, target: D, save: boolean = false, blocker?: DataBlocker<D>, prefix?: string){
		let result = DataProxy.proxy(target, name);
		if(save){
			let item_key = (prefix??'')+name;
			mtec.local.read(item_key, target);
			result.handle.in({
				afterSet(prop, old, valu, handle){
					mtec.local.save(item_key, handle.target);
				}
			});
		}

		if(blocker) result.handle.in(blocker);

		Reflect.set(__Global__, 'data_'+name, result.proxy);

		return result.proxy
	}

	/** 销毁监听代理和记录 */
    public static revoke<D extends Object>(data: D){
		if(DataProxy.id_map.has(data)){
			let id = DataProxy.id_map.get(data);
			let [ins, pxi, handle] = DataProxy.record.get(id);
			[ins, pxi, handle].forEach(i=>DataProxy.id_map.delete(i));
			let path_list: string[] = [];
			let path = handle.path;
			DataProxy.call_pool.forEach((l, p)=>p.startsWith(path)?path_list.push(p):void 0);
			path_list.forEach(p=>DataProxy.call_pool.delete(p));
			handle.revoke();
			DataProxy.record.delete(id);
		}
    }


	private static monitor_call = new Map<string, Array<(path: string, old: any, value: any)=>void>>();
	public static monitor(data: Object, call: (path: string, old: any, value: any)=>void){
		let id = DataProxy.id_map.get(data);

		if(!DataProxy.record.has(id)){
			mtec.log.tag('无法对未代理的实例进行监听: orange', data);
			return void 0;
		}

		let [ins, pxi, hdl] = DataProxy.record.get(id);
		let path = hdl.path;
		if(!DataProxy.monitor_call.has(path)){
			DataProxy.monitor_call.set(path, []);
		}
		DataProxy.monitor_call.get(path).push(call);
	}

	public static free(data: Object){
		let id = DataProxy.id_map.get(data);

		if(!DataProxy.record.has(id)){
			return void 0;
		}

		let [ins, pxi, hdl] = DataProxy.record.get(id);
		let path = hdl.path;
		if(DataProxy.monitor_call.has(path)){
			DataProxy.monitor_call.delete(path);
		}
	}

    /** 监听回调池 */
    private static call_pool: Map<string, Function[]> = new Map();
    /**
     * 注册监听
     * @param data 要监听的数据
     * @param key 要监听的字段
     * @param call 更新时触发的回调函数
     * @param update 是否在注册时触发回调
     */
    public static follow<D extends Object, K extends mtec.OmitKeys<D, Function>>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void, update: boolean = true){
        let handle: PxiHandle<D>;
        if(DataProxy.id_map.has(data)){
			handle = DataProxy.record.get(DataProxy.id_map.get(data))[2];
		}else{
			mtec.log.tag('无法对未代理的实例进行监听: orange', data);
			return void 0;
		}

        let path = handle.path + '.' + key.toString();
		if(!DataProxy.call_pool.has(path)){
			DataProxy.call_pool.set(path, []);
		}
		DataProxy.call_pool.get(path).push(call);

        if(update){
			let [target, proxy] = DataProxy.record.get(DataProxy.id_map.get(data));
			call(target[key], proxy[key]);
		}

        return {path, call} as {path: string, call: Function};
    }

	/** 注销监听 */
	public static cancel(info: ReturnType<typeof DataProxy.follow>){
		if(!DataProxy.call_pool.has(info.path)){
			return void 0;
		}

		let list = DataProxy.call_pool.get(info.path);

		if(list.length > 0){
			list.splice(list.indexOf(info.call), 1);
		}

		if(list.length <= 0){
			DataProxy.call_pool.delete(info.path);
		}
	}

	/**
     * 取消指定的监听
     * @param data 要监听的数据
     * @param key 要监听的字段
     * @param call 更新时触发的回调函数
	 */
	public static out<D extends Object, K extends mtec.OmitKeys<D, Function>>(data: D, key: K, call: (old_value: D[K], new_value: D[K])=>void){
        let handle: PxiHandle<D> = DataProxy.record.get(DataProxy.id_map.get(data))[2];

		if(handle){
			let path = handle.path + '.' + key.toString();
			DataProxy.cancel({path, call});
		}
	}

	/** 指定的路径是否被监听 */
	public static hasFollow<D extends Object, K extends mtec.OmitKeys<D, Function>>(data: D, key: K){
		let id = DataProxy.id_map.get(data);
		if(id===undefined){
			return false;
		}

		let handle = DataProxy.record.get(id)[2];
		if(handle===undefined){
			return false;
		}

		let path = handle.path + '.' + key.toString();
		let list = DataProxy.call_pool.get(path);
		return list===undefined ? false : list.length > 0;
	}


	/** 是否有任务正在执行中 */
	private static tasking: boolean = false;
	/** 更新日志记录 */
	private static record_map = new Map<string, [any, any]>();

	/** 动态延迟，为了解决子节点并发更新时，父节点更新的重复触发造成的性能浪费，逻辑上不能完全防止，目前没有发现异常，暂时如此 */
	private static delay = (()=>{
		let c = createCountAverageIns();
		c.add(30);
		return c;
	})();
	private static delay_stamp = performance.now();
	private static updateDelay(){
		let now = performance.now();
		let diff = now-DataProxy.delay_stamp;
		DataProxy.delay_stamp = now;
		if(diff<=30 && diff>0) DataProxy.delay.add(diff);
	}
	// ⬆⬆⬆ 该段代码的是为了防止，自节点的并发更新，导致父节点的更新事件重复触发的问题

	/** 打包生成任务 */
	private static packaging(){
		if(DataProxy.record_map.size===0) return void 0;

		let record = Array.from(DataProxy.record_map);
		DataProxy.record_map.clear();

		let list = record.filter(el=>DataProxy.call_pool.has(el[0])).map(item=>{
			return {
				path: item[0],
				data: item[1]
			}
		});

		if(list.length>0) DataProxy.taskLoop(list);
		else DataProxy.tasking = false;
	}

	/** 循环执行任务列表中的任务 */
	private static async taskLoop(list: {path: string, data: [any, any]}[]){
		while(list.length>0){
			let task = list.shift();
			//if(!task.path.includes('clock')) abi.log.tag('run task: green', task.path, ...task.data);
			await Promise.allSettled(DataProxy.call_pool.get(task.path).map(f=>new Promise((s, j)=>{
				f(...task.data);
				s(void 0);
			})));
			DataProxy.distributeTask(task);
		}

		if(DataProxy.record_map.size > 0) DataProxy.packaging();
		else DataProxy.tasking = false;
	}

	/** 触发一个任务支线 */
	private static async distributeTask(task: {path: string, data: [any, any]}){
		let path_list = task.path.split('.');
		// 触发被标记的代理监听
		if(DataProxy.monitor_call.has(path_list[0])) DataProxy.monitor_call.get(path_list[0]).forEach(f=>f(task.path, ...task.data));
	}

	/**
	 * 发布更新日志
	 * @param path 更新路径
	 * @param old 原始值
	 * @param value 更新值
	 */
	public static publish(path: string, old: any, value: any){
		//abi.log.tag('publish-->:blue', DataTransfer.tasking, path, JSON.stringify([old, value]));

		DataProxy.record_map.set(path, [old, value]);

		if(!DataProxy.tasking){
			DataProxy.tasking = true;
		// ⬇⬇⬇ 该段代码的是为了防止，字段的并发更新，导致父节点的更新事件重复触发的问题
			let id = setTimeout((dm: typeof DataProxy)=>{
				dm.packaging();
				clearTimeout(id);
			}, DataProxy.delay.average, DataProxy);
		}else DataProxy.updateDelay();
		// ⬆⬆⬆ 该段代码的是为了防止，字段的并发更新，导致父节点的更新事件重复触发的问题
	}
}

function createCountAverageIns(){
    return new ((()=>{
        if(Reflect.has(window ?? globalThis, 'hnc') && mtec.CountAverage){
            return mtec.CountAverage;
        }else{
            return class{
                private count = 0;
                private _average: number;

                constructor(init?: number){
                    if(init) this.add(init);
                }

                public get average(){
                    return this._average ?? 0;
                }

                public add(value: number){
                    this.count++;
                    this._average = this.average + (value - this.average) / this.count;
                    return this._average;
                }

                public clean(){
                    this.count = 0;
                    this._average = 0;
                }
            }
        }
    })())();
}
