/**  
 * @since Last modified: 2024-09-10 09:46:00
 * @class Observe
 * @description Agent and monitoring object.Include:add , edit , delete , get , new and apply.
 * @param {object|array} obj - Object to be listened.    
 * @param {Object} [options] - Parameters 
 * @augments Common
 * @see {@link https://codepen.io/axui/pen/MWLreGx|demo @ codepen}
 * @example
 * let listen = new ax.Observe(obj);
 * listen.ins.on('complete',()=>{})
 */
import getFormData from '../../../../src/scripts/utils/getFormData';
import { T_fn, T_obj, T_required } from '../../../types/ax';
import { T_observe } from '../../../types/observe';
import optObserve from '../options/optObserve';
import getDataType from '../utils/getDataType';
import isProxy from '../utils/isProxy';
import pipe from '../utils/pipe';
import unique from '../utils/unique';
import PubBase from './PubBase';
import PubCommon from './PubCommon';
import PubListen from './PubListen';
class Observe extends pipe(PubListen, PubCommon)(PubBase) {
    options: T_required<T_observe> | T_obj = {};
    dataType: any;
    completeCount: any;
    reactCount: any;
    proxys: any;
    fullMethods: any;
    methods: any;
    output: any;
    targetData: any;
    static hostType = 'data';
    constructor(data: any[] | T_obj, options?: T_observe, initial: boolean = true) {
        super();
        this.ready({
            options,
            maps: optObserve,
            host: data,
        });
        this.timestamp = Date.now();
        this.proxys = []
        this.output = { ins: this, target: this.targetData };
        initial && this.init() ;
        return this.output;
    }
    async init(cb?: T_fn): Promise<any> {
        super.listen({ name: 'launch' });
        this.options.b4Init && await this.options.b4Init.call(this);
        this.dataType = getDataType(this.targetData);
        if (!this.targetData || !['Object', 'Array', 'Function'].includes(this.dataType)) {
            console.warn('The typeof target data is required to be object or function!')
            this.output.proxy = this.targetData;
        }
        /**
        * @name Observe.completeCount
        * @description The total number of complete.Use that parameter with options.once.
        * @type number
        */
        this.completeCount = 0;
        /**
       * @name Observe.reactCount
       * @description The total number of responses.
       * @type number
       */
        this.reactCount = 0;
        /**
         * @name Observe.keys
         * @description The set of all keys listened to when complete.
         * @type array
         */
        this.keys = [];
        /**
         * @name Observe.fullMethods
         * @description All listening methods collection.
         * @property {function} get - listen get data.
         * @property {function} set - listen set data.
         * @property {function} delete - listen delete data.
         * @type object
         */
        this.fullMethods = {
            get: (target: any, key: string | number, proxy: any) => {
                if (key === '_isProxy') return true;
                let value = target[key],
                    baseProps = { target, key, value, raw: value, proxy };
                this.keys.push(key);
                super.listen({ name: 'get', params: [{ ...baseProps, type: 'get' }] });
                super.listen({ name: 'crud', params: [{ ...baseProps, type: 'get' }] });
                super.listen({ name: 'trigger', params: [{ ...baseProps, type: 'get' }] });
                return Reflect.get(target, key, proxy);
            },
            set: (target: any, key: string | number, value: any, proxy: any) => {
                if (JSON.stringify(target[key]) == JSON.stringify(value)) {
                    return true
                }
                let valueType = getDataType(value),
                    raw = target[key],
                    baseProps = { target, key, value, raw, proxy },
                    handleType = target[key] === undefined ? 'add' : 'edit';
                if ((valueType === 'Object' || valueType === 'Array') && this.options.deep && !isProxy(value)) {
                    value = this.addProxy(value);
                }
                if (this.options.accept) {
                    Reflect.set(target, key, value, proxy);
                }
                this.keys.push(key);
                super.listen({ name: 'crud', params: [{ ...baseProps, type: 'set' }] });
                super.listen({ name: 'set', params: [{ ...baseProps, type: 'set' }] });
                super.listen({ name: handleType, params: [{ ...baseProps, type: handleType }] });
                super.listen({ name: 'trigger', params: [{ ...baseProps, type: 'set' }] });
                this.reactCount++;
                this.complete(this.reactCount);
                return true;
            },
            delete: (target: any, key: string | number) => {
                let value = target[key],
                    baseProps = { target, key, value, raw: value };
                if (this.options.accept) {
                    Reflect.deleteProperty(target, key);
                }
                this.keys.push(key);
                super.listen({ name: 'delete', params: [{ ...baseProps, type: 'delete' }] });
                super.listen({ name: 'crud', params: [{ ...baseProps, type: 'delete' }] });
                super.listen({ name: 'trigger', params: [{ ...baseProps, type: 'delete' }] });
                this.reactCount++;
                this.complete(this.reactCount);
                return true;
            },
            construct: (target: T_fn, args: any[], proxy: any) => {
                let baseProps = { target, args, proxy };
                super.listen({ name: 'new', params: [{ ...baseProps, type: 'new' }] });
                super.listen({ name: 'trigger', params: [{ ...baseProps, type: 'new' }] });
                this.reactCount++;
                this.complete(this.reactCount);
                return Reflect.construct(target, args, proxy);
            },
            apply: (target: T_fn, pointer: any, args: any[]) => {
                let baseProps = { target, pointer, args };
                super.listen({ name: 'apply', params: [{ ...baseProps, type: 'apply' }] });
                super.listen({ name: 'trigger', params: [{ ...baseProps, type: 'apply' }] });
                this.reactCount++;
                this.complete(this.reactCount);
                return Reflect.apply(target, pointer, args);
            }
        }
        /**
         * @name Observe.methods
         * @description User listening methods collection.
         * @type object
         */
        this.methods = {};
        for (let k in this.fullMethods) {
            let key = k === 'delete' ? 'deleteProperty' : k
            this.methods[key] = this.fullMethods[k]
        }
        /**
         * @name Observe.hostProxy
         * @description Primary Agent Object.
         * @type object
         */
        this.hostProxy = this.options.deep ? this.deepProxy(this.targetData) : this.addProxy(this.targetData);
        this.output.proxy = this.hostProxy;
        super.listen({ name: 'init', cb });
        return this;
    }
    deepProxy(data: any) {
        let dateType = getDataType(data);
        if (['Object', 'Array', 'Function'].includes(dateType)) {
            for (let k in data) {
                let val = data[k],
                    valType = getDataType(val);
                if (data.hasOwnProperty(k) && ['Object', 'Array', 'Function'].includes(valType)) {
                    data[k] = this.deepProxy(val);
                }
            }
            return this.addProxy(data);
        }
        return data;
    }
    complete(nowReact: number) {
        setTimeout(() => {
            if (nowReact === this.reactCount) {
                if (this.options.once && this.completeCount !== 0) {
                    return false;
                }
                this.keys = unique(this.keys.filter((k: any) => typeof k !== 'symbol'));
                super.listen({ name: 'complete', params: [{ target: this.targetData, proxy: this.hostProxy, keys: this.keys, type: 'complete' }] });
                this.completeCount++;
                this.keys = [];
            }
        }, 0);
    }
    addProxy(obj: T_obj | any[]) {
        let revProxy = Proxy.revocable(obj, this.methods);
        this.proxys.push(revProxy);
        return revProxy.proxy;
    }
    destroy() {
        this.proxys.forEach((k: any) => {
            k.revoke();
        })
    }
}
export default Observe;
