import { getStack, readErrObj, toString } from "../common/base";
import { Basic } from "../common/basic";
import { renderParams, renderParamsWithFinal } from "../common/params";
import { LogType } from "../common/logger";
import { Device, DeviceOpt } from "./device";
import { Callbacks } from "./callbacks";
import modules from "./modules";
import { CallType, MessageType } from "../typings/enums";

/**
 * 核心代理处理
 */
export class Core extends Basic {
  device: Device;
  callbacks: Callbacks;

  constructor(opt: DeviceOpt) {
    super(opt);

    this.device = new Device(opt);
    this.callbacks = new Callbacks(opt);
    this.init();
  }

  /**
   * 调用sdk接口
   * path: advance.sendWup
   */
  async callSDK(path: string, params: any[]) {
    const defFun = getStack(modules, path);

    if (defFun) {
      return await defFun(params, this.device);
    }

    const fun = getStack(this.device.sdk, path);
    if (!fun) throw new Error(`unknow api ${path}`);
    return await fun(...params);
  }

  /**
   * 遍历参数，识别并替换回调
   */
  renderParamsCB(info: ReqMessage) {
    const { apiName, params, callType, eventType, unikey } = info.payload || {};

    switch (callType) {
      // 事件监听
      case CallType.ON:
        this.logger.log(`render on`, callType, eventType);
        // 遍历params 提取 cbID 并注册到监听
        return renderParams(
          params,
          (params) => this.callbacks.isCbId(params), // 识别出参数是cbId
          (cbId) => {
            const cb = (message: any) => {
              this.logger.log(`tigger ${cbId}`, message);
              this.device.emit({
                type: MessageType.EMIT_EVENT,
                payload: {
                  cbId,
                  apiName,
                  message: toString(message || ""),
                  unikey,
                },
              });
            };
            this.callbacks.add(eventType, cbId, cb);
            return cb;
          }
        );
      case CallType.OFF:
        // 移除监听
        return renderParamsWithFinal(
          params,
          (params) => this.callbacks.isCbId(params), // 匹配cbId
          (cbId) => this.callbacks.remove(eventType, cbId), // 匹配后直接移除缓存
          (matched) => !matched && this.callbacks.remove(eventType) // 未匹配清除type下所有
        );
      default:
        return params;
    }
  }

  /**
   * 打印webview内的日志
   * 默认将 error 打印到 native，其他打印到控制台
   * 开启 debug 后均打印到 native
   */
  callConsole(path: string, params: any[]) {
    try {
      // debug 开启日志写到 native
      if (path.endsWith(LogType.ERROR) || this.logger.config.debug) {
        const { logger } = this.device.sdk;
        switch (true) {
          case path.endsWith(LogType.LOG):
            return logger.debug(...params);
          case path.endsWith(LogType.INFO):
            return logger.info(...params);
          case path.endsWith(LogType.WARN):
            return logger.warn(...params);
          case path.endsWith(LogType.ERROR):
            return logger.error(...params);
        }
      } else {
        const fun = getStack(console, path);
        fun && fun(...params);
      }
    } catch (err) {
      console.warn("console fail", err);
    }
  }

  /**
   * 代理接受初始化
   */
  init() {
    this.device.init((data: ReqMessage) => {
      // 收到调用消息
      const { type, payload } = data;

      const { reqId, apiName, params, unikey } = payload;
      const path = apiName.replace("hyExt.", ""); // 提取可用 path

      // console 模块单独处理 扩展打印模块
      if (path.startsWith("console")) {
        return this.callConsole(path, params);
      }

      // 重新修改 params
      const nParams = this.renderParamsCB(data);
      this.logger.log(`call ${reqId} with`, nParams);

      // 调 sdk 接口
      this.callSDK(path, nParams)
        .then((data: any) => {
          this.logger.log(`call ${reqId} success`, data);
          this.device.emit({
            type: MessageType.CALL_RSP,
            payload: { reqId, apiName, rsp: toString(data || ""), unikey },
          });
        })
        .catch((err: any) => {
          const errObj = readErrObj(err, err);
          this.logger.log(`call ${reqId} fail`, errObj);
          this.device.emit({
            type: MessageType.CALL_RSP,
            payload: { reqId, apiName, rsp: "", err: errObj, unikey },
          });
        });
    });
    this.logger.log("proxy inited");
  }

  destroy() {
    this.callbacks.clean();
    this.device.destroy();
    this.logger.log("proxy destroied");
  }
}
