import { AccountInfo, LuffaTronSdk, MethodName } from '../index';
import type { IMessageData, TronSDKEventListenersType, TronSDKEventPayload, TronSDKEventType } from './types';
import { TronSDKEvent, IResponseMessageData } from './types';
import { isLuffaMiniProgramWebview, isLuffaMiniProgram, isLuffa, networkMap, getChainIdByName } from '../utils';

export class PostMessage {
  private static _instance: PostMessage;
  callbacks: {
    [key: string]: (data: unknown) => void;
  } = {};
  private listeners: TronSDKEventListenersType = {};

  constructor() {
    if (PostMessage._instance) return PostMessage._instance;
    PostMessage._instance = this;
    if (isLuffa()) {
      window.tronWallet = {
        sendResponse: this.sendResponse.bind(this),
      };
    }
  }
  // wallet emit sdk
  // SDK processing method itself
  private readonly receive = (msg: { data: IResponseMessageData }) => {
    const eventType = msg.data.methodName as TronSDKEventType | MethodName;
    // sdk callbacks
    if (this.callbacks[msg.data.uuid + eventType]) {
      this.callbacks[msg.data.uuid + eventType](msg.data.data);
      delete this.callbacks[msg.data.uuid + eventType];
    }

    // Some events are being monitored by dapp and require running a monitoring callback
    switch (eventType) {
      case TronSDKEvent.NETWORK_CHANGE: {
        let network = msg.data.data.network;
        if (!network) break;
        if (isLuffaMiniProgram()) {
          network = msg.data.data;
        }
        this.emit(eventType, network);
        this.emit(TronSDKEvent.CHAIN_CHANGED, `0x${getChainIdByName(network)?.toString(16)}`);

        break;
      }

      // wallet to sdk send onAccountChange | connect
      // sdk to dapp send event
      // CONNECT and ACCOUNT_CHANGE return account as AccountAddress
      case TronSDKEvent.CONNECT:
      case TronSDKEvent.ACCOUNT_CHANGE: {
        const accountInfo: AccountInfo = {
          ...msg.data.data,
        };
        if (msg?.data?.data?.account) {
          LuffaTronSdk.setAccountAddress(msg.data.data.account);
        } else {
          LuffaTronSdk.setAccountAddress(null);
        }
        this.emit(eventType, accountInfo);
        break;
      }

      case TronSDKEvent.DISCONNECT: {
        LuffaTronSdk.setAccountAddress(null);
        this.emit(eventType, msg.data.data);
        break;
      }

      default:
        this.emit(eventType as TronSDKEventType, msg.data.data);
        break;
    }
  };

  readonly addListener = <K extends TronSDKEventType>(
    methodName: K,
    callback: (payload: TronSDKEventPayload<K>) => void
  ) => {
    if (!this.listeners[methodName]) {
      this.listeners[methodName] = [];
    }
    this.listeners[methodName].push(callback);
  };

  readonly removeListener = <K extends TronSDKEventType>(
    methodName: K,
    callback?: (payload: TronSDKEventPayload<K>) => void
  ) => {
    if (callback) {
      const index = this.listeners[methodName]?.indexOf(callback) ?? -1;
      if (index > -1) {
        this.listeners?.[methodName]?.splice(index, 1);
      }
    } else {
      this.listeners[methodName] = [];
    }
  };

  readonly emit = <K extends TronSDKEventType>(methodName: K, payload: TronSDKEventPayload<K>) => {
    this.listeners?.[methodName]?.forEach((d) => d(payload));

    this.emitTronEvent(methodName, payload);
  };

  private emitTronEvent<K extends TronSDKEventType>(methodName: K, payload: TronSDKEventPayload<K>) {
    if (typeof window === 'undefined' || !window.tron) return;

    let tronPayload: any;
    switch (methodName) {
      case TronSDKEvent.CONNECT:
      case TronSDKEvent.ACCOUNT_CHANGE:
        const account = (payload as AccountInfo)?.account || (payload as AccountInfo)?.address;
        tronPayload = {
          action: 'accountsChanged',
          data: {
            address: account,
          }
        };
        break;
      case TronSDKEvent.DISCONNECT:
        tronPayload = undefined;
        break;
      default:
        tronPayload = payload;
        break;
    }

    try {
      const event = new MessageEvent('message', {
        data: {
          isTronLink: true,
          message: tronPayload,
        }
      });
      window.dispatchEvent(event);
    } catch (error) {
      console.warn('Failed to emit tron event:', error);
    }
  }

  readonly sendMessage = (data: IMessageData, callback?: (data: any) => void) => {
    try {
      if (isLuffa()) {
        this.sendLuffaMessage(data, callback);
        return;
      }
      if (isLuffaMiniProgram() || isLuffaMiniProgramWebview()) {
        this.sendMiniProgramMessage(data, callback);
        return;
      }
    } catch (error) {
      console.error('sendMessage error: ', error);
    }
  };

  private sendLuffaMessage(data: IMessageData, callback?: (data: any) => void) {
    const initData = data?.initData || LuffaTronSdk.getIninData();
    (data.initData = {
      ...(initData || {}),
      network: networkMap[initData.network] ?? initData.network,
    }),
      (data.from = LuffaTronSdk.getAccountAddress());
    if (callback) {
      this.callbacks[data.uuid + data.methodName] = callback;
    }
    console.log('luffa tron sendLuffaMessage: ', data);
    if (window?._tronWallet) {
      window._tronWallet?.sendMessage(JSON.stringify(data));
    } else if (window?.webkit && window?.webkit?.messageHandlers?._tronWallet) {
      window.webkit.messageHandlers._tronWallet?.postMessage(data);
    }
  }

  private sendResponse(response: string) {
    console.log('luffa tron sendResponse: ', response);
    const responseData = JSON.parse(response);
    console.log('luffa tron responseData: ', responseData);
    this.receive({
      data: responseData,
    });
  }

  private sendMiniProgramMessage(data: IMessageData, callback?: (data: any) => void) {
    let funName = 'invokeNativePlugin';
    const accountAddress = LuffaTronSdk.getAccountAddress();
    const initData = LuffaTronSdk.getIninData();
    let api_name = 'luffaWebRequest';
    let params = {
      api_name,
      data: {
        func: data.methodName,
        chainType: 'endless',
        ...data,
        initData: {
          ...initData,
          network: networkMap[initData.network] ?? initData.network,
        },
        from: accountAddress,
      },
    };
    let callbackSuccessFun = (res: any) => {
      callback &&
        callback({
          status: 'success',
          ...(res.data || {}),
        });
    };
    let callbackErrorFun = (res: any) => {
      callback &&
        callback({
          status: 'error',
          ...(res.data || {}),
        });
    };
    switch (data.methodName) {
      case MethodName.CONNECT:
      case MethodName.GETACCOUNT:
        callbackSuccessFun = (res) => {
          LuffaTronSdk.setAccountAddress(res.data.account);
          callback &&
            callback({
              account: res?.data?.address || '',
              ...(res.data || {}),
            });
        };
        callbackErrorFun = (res) => {
          callback &&
            callback({
              account: '',
            });
        };
        break;
      case MethodName.SIGN_MESSAGE:
      case MethodName.SIGN_TRANSACTION:
      case MethodName.SIGN_BUILD_TRANSACTION:
      case MethodName.SIGN_AND_SUBMIT_TRANSACTION:
      case MethodName.EVM_APPROVE:
        callbackSuccessFun = (res: any) => {
          callback &&
            callback({
              status: 'success',
              ...(res.data || {}),
            });
        };
        callbackErrorFun = (res: any) => {
          callback &&
            callback({
              status: 'error',
              ...(res.data || {}),
            });
        };
        break;

      default:
        break;
    }
    console.log('luffa tron funName: ', funName);
    console.log('luffa tron params: ', params);
    if (isLuffaMiniProgram()) {
      wx[funName]({
        ...params,
        complete: (res: any) => {
          console.log(`luffa tron wx ${funName} res: `, res);
          if (res.status === undefined) {
            callback &&
              callback({
                ...res,
              });
          } else if (res.status === 'success') {
            callbackSuccessFun(res);
            this.receive({
              data: res,
            });
          } else {
            callbackErrorFun(res);
          }
        },
      });
    } else if (isLuffaMiniProgramWebview()) {
      window.WeixinJSBridge?.invoke(funName, params, (res) => {
        console.log(`luffa tron WeixinJSBridge ${funName} res: `, res);
        if (res.status === undefined) {
          callback &&
            callback({
              ...res,
            });
        } else if (res.status === 'success') {
          callbackSuccessFun(res);
          this.receive({
            data: res,
          });
        } else {
          callbackErrorFun(res);
        }
      });
    }
  }
}
