package com.anythink.reactnative.event;

import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.anythink.reactnative.utils.Const;
import com.anythink.reactnative.utils.MsgTools;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.util.Map;

/**
* Native → JS only event channel.
 *
* All uplink callbacks are issued through this, according to 8 fixed callName + unified payload; **all emit in the main thread**
* (RN's JSI/event dispatch requires the main thread; this is the Android counterpart of the iOS `dispatch_async(main)` daemon).
* Disable second set of EventBus.
 */
public class ATReactNativeEventEmitter {

    private static volatile ATReactNativeEventEmitter sInstance;
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    private volatile ReactApplicationContext mReactContext;

    private ATReactNativeEventEmitter() {
    }

    public static ATReactNativeEventEmitter getInstance() {
        if (sInstance == null) {
            synchronized (ATReactNativeEventEmitter.class) {
                if (sInstance == null) {
                    sInstance = new ATReactNativeEventEmitter();
                }
            }
        }
        return sInstance;
    }

    public void setReactContext(ReactApplicationContext reactContext) {
        mReactContext = reactContext;
    }

    /** Simplified variant: event without extraDic / errorMsg. */
    public void sendCallback(String callName, String callbackName, String placementId) {
        sendCallback(callName, callbackName, placementId, null, null, null);
    }

    /**
* Send a callback to JS.
     *
* @param callName One of 8 fixed callName (Const.CallbackMethodCall.*)
* @param callbackName Specific callback name (Const.*Callback.*)
* @param placementId Advertising slot ID; the Init class can be an empty string
* @param extraDic Flattened TUAdInfo (Map); nullable
* @param requestMessage The full text of AdError; pass null when successful (complement "" when emit)
* @param extra additional Boolean field (isDeeplinkSuccess / isTimeout); can be empty
     */
    public void sendCallback(final String callName, String callbackName, String placementId,
                             Map<String, Object> extraDic, String requestMessage, Map<String, Object> extra) {
        try {
            final WritableMap payload = Arguments.createMap();
            payload.putString(Const.CallbackKey.callbackName, callbackName);
            payload.putString(Const.CallbackKey.placementID, placementId != null ? placementId : "");

            if (extraDic != null) {
                payload.putMap(Const.CallbackKey.extraDic, Arguments.makeNativeMap(extraDic));
            }

            payload.putString(Const.CallbackKey.requestMessage, requestMessage != null ? requestMessage : "");

            if (extra != null) {
                for (Map.Entry<String, Object> entry : extra.entrySet()) {
                    String key = entry.getKey();
                    Object value = entry.getValue();
                    if (value instanceof Boolean
                            && (TextUtils.equals(key, Const.CallbackKey.isDeeplinkSuccess)
                            || TextUtils.equals(key, Const.CallbackKey.isTimeout))) {
                        payload.putBoolean(key, (Boolean) value);
                    } else if (value instanceof String && TextUtils.equals(key, Const.CallbackKey.adId)) {
// Native list with multiple ads: adId top level is exposed, JS is diverted to the corresponding cell according to placementID+adId
                        payload.putString(key, (String) value);
                    }
                }
            }

            emitOnMain(callName, callbackName, payload);
        } catch (Throwable e) {
            MsgTools.printMsg("sendCallback error: " + callbackName + ", " + e.getMessage());
        }
    }

    private void emitOnMain(final String callName, final String callbackName, final WritableMap payload) {
        Runnable emit = new Runnable() {
            @Override
            public void run() {
                try {
                    ReactApplicationContext ctx = mReactContext;
                    if (ctx != null && ctx.hasActiveReactInstance()) {
                        ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                                .emit(callName, payload);
                        MsgTools.printMsg("emit " + callName + "/" + callbackName);
                    } else {
                        MsgTools.printMsg("emit skipped: react context not active, " + callName);
                    }
                } catch (Throwable e) {
                    MsgTools.printMsg("emit error: " + callName + ", " + e.getMessage());
                }
            }
        };
        if (Looper.myLooper() == Looper.getMainLooper()) {
            emit.run();
        } else {
            mMainHandler.post(emit);
        }
    }
}
