import Actions from "./enum/Actions.js"
import Codes from "./enum/Codes.js"
import util from "./util.js"
import Logger from "./Logger.js"

const logger = new Logger('events')
export default class EventsManager extends EventEmitter {
    constructor() {
        super(...arguments);
        E(this, "events", new Map);
        E(this, "specialEvents", new Map)
    }

    remove(traceId, code, signal, needDelete) 
    {
        if (this.specialEvents.has(traceId) && !needDelete && code === Codes.Success) return;

        this.events.get(traceId) && (
            this.emit(traceId, {code, data:signal}),
            this.events.delete(traceId),
            this.specialEvents.delete(traceId)
        )
    }

    async track(e, t) 
    {
        const traceId = e.traceId;

        this.emitTraceIdToDecoder(e);

        const { sampleRate, noReport=!1, special } = t || {};
        if (sampleRate && Math.random() > sampleRate) return Promise.resolve();
        const s = Actions[e.event] + "Action"
        const tag = e.tag;

        // 暂存traceId
        this.events.set(traceId, !0),
        special && this.specialEvents.set(traceId, !0);
        
        const startTime = Date.now();
        let c = null;
        return new Promise((resolve, reject) => 
        {
            if (noReport)
                return this.off(traceId),
                    this.events.delete(traceId),
                    resolve(void 0);

            // 移除traceId。events.delete在emit之后执行
            this.on(traceId, ({ code, data, msg }) => 
            {
                if (code === Codes.Success)
                    resolve(data),
                    this.off(traceId),
                    logger.infoAndReportMeasurement({ metric: s, tag, extra: e.extra, startTime, traceId });
                else {
                    if (code === Codes.ActionMaybeDelay) return;
                    if (code === Codes.DoActionBlocked && e.event === Actions.Rotation) {
                        logger.debug(s + " response code: " + code);
                        return
                    }
                    const CodeError = util.getErrorByCode(code)
                      , error = new CodeError(msg);
                    this.off(traceId),
                    reject(error),
                    this.emit("actionResponseError", { error, event: e, tag }),
                    logger.infoAndReportMeasurement({ metric: s, tag, extra: e.extra, error, startTime, traceId })
                }
            });

            // 超时报错
            const time = e.timeout || 2e3;
            c = window.setTimeout(() => 
            {
                if (c && clearTimeout(c), !this.events.get(traceId)) return;

                const error = new ActionResponseTimeoutError(`${s} timeout in ${d}ms`);
                this.emit("actionResponseTimeout", { error, event: e, tag }),
                reject(error),
                this.events.delete(traceId),
                this.off(traceId),
                logger.infoAndReportMeasurement({ metric: s, tag, extra: e.extra, error, startTime, traceId })
            }, time)
        })
    }

    emitTraceIdToDecoder(e) {
        if (
            e.event === Actions.Rotation || 
            e.event === Actions.Clicking || 
            e.event === Actions.GetOnVehicle || 
            e.event === Actions.GetOffVehicle
        ) {
            const t = {
                [Actions.Rotation]: "Rotation",
                [Actions.GetOnVehicle]: "GetOnVehicle",
                [Actions.GetOffVehicle]: "GetOffVehicle",
                [Actions.Clicking]: "MoveTo"
            };
            this.emit("traceId", {
                traceId: e.traceId,
                timestamp: Date.now(),
                event: t[e.event]
            })
        }
    }
}

const eventsManager = new EventsManager();
export { eventsManager };