/**
 * @since Last modified: 2024/06/25 22:06:27
 * @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 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) {
    constructor(data, options, inital = true) {
        super();
        this.options = {};
        this.ready({
            options,
            maps: optObserve,
            host: data,
        });
        this.timestamp = new Date().getTime();
        this.proxys = [];
        this.output = { ins: this, target: this.targetData };
        inital ? this.init() : null;
        return this.output;
    }
    init(cb) {
        super.listen({ name: 'start' });
        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, key, proxy) => {
                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, key, value, proxy) => {
                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, key) => {
                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, args, proxy) => {
                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, pointer, args) => {
                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) {
        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) {
        setTimeout(() => {
            if (nowReact === this.reactCount) {
                if (this.options.once && this.completeCount !== 0) {
                    return false;
                }
                this.keys = unique(this.keys.filter((k) => 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) {
        let revProxy = Proxy.revocable(obj, this.methods);
        this.proxys.push(revProxy);
        return revProxy.proxy;
    }
    destroy() {
        this.proxys.forEach((k) => {
            k.revoke();
        });
    }
}
Observe.hostType = 'data';
export default Observe;
