import sdk from '@tcwd/weapps-sdk/lib/app-h5-sdk';

import {
  createWebApp,
  IWebAppAPI,
  _ACTIONS_KEY as ACTIONS_KEY,
  _ROUTER_KEY as ROUTER_KEY,
  // _WEDA_CLOUD_SDK as WEDA_CLOUD_SDK,
} from '@cloudbase/weda-client';
import { runtime } from '../utils/adapter/index';
import { getEnumOptions } from '../utils/initGlobalVar';
import { get as lodashGet } from 'lodash-es';
import { OFFICIAL_LIB_KEY } from '@cloudbase/cals';
import { observable } from 'mobx';
import { UserWidget, bindFuncObj, createComputed } from '../utils';
import * as querystring from 'query-string';

interface IBaseState {
  state: {
    [key: string]: any;
  };
  computed: {
    [key: string]: any;
  };
  dataset?: {
    state?: {};
    params?: {};
    query?: {};
  };
}

// enum EPLATFORM {
//   WEB = 'WEB',
//   /**
//    * 拼写错误
//    * 兼容性无法修改
//    */
//   MINIPROGRAM = 'MINIPROGRAME',
// }

const DEFAULT_INIT_DATA = {
  processAction: (key, action) => {
    switch (key) {
      case ACTIONS_KEY.showModal: {
        return function (obj) {
          const showModal = runtime[`@weapps-materials-main-${OFFICIAL_LIB_KEY}`]?.actions?.showModal;
          const isMobile = app?.__internal__?.isMobile;
          if (isMobile && !isMobile() && showModal) {
            return showModal({ data: obj, _origin: action });
          }
          return action(obj);
        };
      }
      case ACTIONS_KEY.showToast: {
        return function (obj) {
          const showMessage = runtime[`@weapps-materials-main-${OFFICIAL_LIB_KEY}`]?.actions?.showMessage;
          if (showMessage) {
            return showMessage({ data: obj, _origin: action });
          }
          return action(obj);
        };
      }
    }
    return action;
  },
};

export const subPackageName = '';
export let app = createGlboalApi(DEFAULT_INIT_DATA);
export const $w = new Proxy(app.__internal__.$w, {
  get(_, prop: string) {
    if (prop === '$app' || prop === 'app') {
      return app;
    }
    return app.__internal__.$w?.[prop];
  },
});

export function registerApp(_app = createGlboalApi(DEFAULT_INIT_DATA)) {
  _injectApp2Runtime(_app, true);
  app = _app;
}

mountAPIs(sdk);
export interface IAppAPI extends IWebAppAPI, IBaseState {
  domain: string;
  app?: IAppAPI;
  $app?: IAppAPI;
  mainApp?: IAppAPI;
  common: {};
  enumOptions?: {};
  /**
   * 内置方法
   */
  setState: (state) => void;
  invoke: (params) => void;
  utils: IWebAppAPI['utils'] & {
    formatEnum: (path, optionname, app) => string;
  };
}

function createGlboalApi(
  initData: {
    id?: string;
    domain?: string;
    appConfig?: {
      id?: string;
      envId?: string;
      envVersion?: 'preview' | 'production';
      staticResourceDomain?: string;
      pages: { id: string }[];
    };
    processAction?: (key, action) => any;
  } = {},
): IAppAPI {
  const webApp = createWebApp(initData);
  const globalAPI: IAppAPI = new Proxy(
    Object.assign(webApp, {
      domain: initData.domain || '',
      state: {},
      computed: {},
      dataset: {},
      common: {},
      enumOptions: {},
      // i18n,
      ...webApp,
      utils: {
        ...webApp.utils,
        formatEnum: (path, optionname, app) => {
          let isSingle = Array.isArray(path);
          // 这个app是后来添加进去的
          let options = app?.enumOptions;
          let parseOptions = options[optionname] ? options[optionname] : getEnumOptions(optionname);
          if (parseOptions === '') {
            return !isSingle ? path : path.join(',');
          }
          let multiTmp = [];
          let value = !isSingle
            ? JSON.parse(parseOptions)?.find((item) => item.key === path)?.value
            : JSON.parse(parseOptions)
                ?.filter((item) => path.some((pathValue) => item.key === pathValue))
                .map((item) => multiTmp.push(item.value));
          return !isSingle ? value : multiTmp.join(',');
        },
      },
      /**
       * setState
       */
      setState: (userSetState) => {
        Object.keys(userSetState).forEach((keyPath) => {
          globalAPI.utils.set(globalAPI.dataset.state, keyPath, userSetState[keyPath]);
        });
      },
      /**
       * @deprecated
       */
      invoke(params) {
        const $page = globalAPI.utils.getCurrentPage();
        return $page.invokeComponentMethod(params);
      },
    }),
    {
      get: (obj, prop) => {
        if (prop === 'app' || prop === '$app') {
          return app;
        } else {
          return obj[prop];
        }
      },
      // set: (obj, prop, value) => {
      //   if (prop === 'app' || prop === '$app') {
      //     console.warn('$app 对象中 app/$app 为只读');
      //     return false;
      //   } else {
      //     // 修改 obj
      //     return true;
      //   }
      // },
    },
  );
  _injectApp2Runtime(globalAPI);

  return globalAPI;
}

function _injectApp2Runtime(globalAPI, hard = false) {
  // 给全局挂上 mainApp/subApp
  // The global api exposed to lowcode
  if (subPackageName) {
    globalAPI.mainApp = runtime.$app;
    runtime[`$$subapp_${subPackageName}`] = globalAPI;
  } else {
    // 预览区调试栏可调试
    // 暴露给复合组件，代码组件来引用
    runtime.app = hard ? globalAPI : runtime.app || globalAPI;
    runtime.$app = new Proxy(runtime.app, {
      get(obj, prop) {
        return obj ? obj[prop] : undefined;
      },
      set(obj, prop, value) {
        if (obj) {
          return (obj[prop] = value);
        } else {
          return undefined;
        }
      },
    });

    runtime.$w = new Proxy($w || globalAPI?.__internal__?.$w || {}, {
      get(_, prop: string) {
        const $page = runtime.app?.__internal__?.activePage || runtime.$page;
        if ($page?.__internal__?.$w?.[prop]) {
          return $page?.__internal__?.$w?.[prop];
        }

        if (_[prop]) {
          return _[prop];
        }

        return null;
      },
    });
  }
}

export function removeGlboalApi() {
  if (subPackageName) {
    runtime[`$$subapp_${subPackageName}`] = undefined;
  } else {
    runtime.app = undefined;
    runtime.$app = undefined;
  }
}

export interface IPageAPI extends IBaseState {
  __internal__?: {
    $w?: any;
    active: boolean;
  };
  $page?: IPageAPI;
  id?: string;
  handler: {
    [key: string]: any;
  };
  widgets: {
    [key: string]: any;
  };
  setState: (state) => void;
  setParams: (params) => void;
  /**
   * @deprecated
   */
  invokeComponentMethod: (props: { component: string | UserWidget; method: string; params: any }) => any;
  _dataBinds: {
    [key: string]: any;
  };
}

export function createPageApi(): IPageAPI {
  const $page: IPageAPI = new Proxy(
    {
      __internal__: {
        active: false,
      },
      state: {},
      computed: {},
      handler: {},
      widgets: observable({}),
      dataset: {},
      setState: (userSetState) => {
        Object.keys(userSetState).forEach((keyPath) => {
          app.utils.set($page.dataset?.state, keyPath, userSetState[keyPath]);
        });
      },
      setParams: (_params) => {
        const params = Array.isArray(_params)
          ? _params.reduce((map, meta) => {
              let { key, value } = meta;
              if (key?.startsWith?.('$page.')) {
                key = key.replace(/^\$page\./, '');
              }
              if (key?.trim?.()) {
                map[key?.trim?.()] = value;
              }
              return map;
            }, {})
          : _params;

        Object.keys(params).forEach((keyPath) => {
          app.utils.set($page.dataset?.params, keyPath, params[keyPath]);
        });

        let queryObj;
        try {
          queryObj =
            querystring.parse(((runtime as any)._WEAPPS_HISTORY.location.search || '').split('?')[1] || '') || {};
        } catch (e) {
          queryObj = { ...$page.dataset?.params };
        }

        Object.keys(params)
          .map((keyPath) => {
            const matched = keyPath.match(/(^.*?)[.[]/);
            if (matched) {
              return matched[0];
            }
            return keyPath;
          })
          .forEach((key) => {
            queryObj[key] = $page.dataset?.params?.[key];
          });

        app.redirectTo({
          pageId: $page.id,
          params: queryObj,
        });
      },
      _dataBinds: {},
      invokeComponentMethod({ component, method, params }) {
        let userWidget: UserWidget;
        if (typeof component === 'string') {
          const widgetData = lodashGet($page.widgets, component);
          if (Array.isArray(widgetData)) {
            throw new Error(`调用方法失败：id为${component}的组件拥有多个实例`);
          }
          userWidget = widgetData?._userWidget;
        } else {
          userWidget = component;
        }
        const action = userWidget?.custom?.[method];
        if (typeof action !== 'function') {
          throw new Error(`调用方法失败：未找到id为${userWidget?.sys.id}下的方法${method}`);
        }
        return action(params);
      },
    },
    {
      get(obj, prop) {
        return prop === '$page' ? obj : obj[prop];
      },
    },
  );
  return $page;
}

// 分app 和 wx 挂载app
function mountAPIs(sdks) {
  Object.keys(sdks).forEach((item) => {
    let action = sdks[item];
    if (!(item in ACTIONS_KEY) && !(item in ROUTER_KEY)) {
      app[item] = action;
    }
  });
  return app;
}

export interface ICompositeComponentAPI extends IBaseState {
  __internal__?: {
    $w?: any;
  };
  id: string;
  handler?: {
    [key: string]: any;
  };
  props: {
    [key: string]: any;
  };
  widgets?: {
    [key: string]: any;
  };
  _dataBinds?: {
    [key: string]: any;
  };
  _instanceRef: { current: any };
  compConfig?: Function;
}

export function createCompApi({
  id,
  ref,
  state = observable({}),
  computed = {},
  _dataBinds = {},
  compConfig = {},
  widgets = {},
  node,
  events,
  props,
  handler = {},
  propsData,
}): ICompositeComponentAPI {
  const $comp = new Proxy(
    {
      id,
      __internal__: {
        $w: new Proxy(
          {},
          {
            get(_, prop) {
              // 尝试代理页面级别组件实例
              let pageWidget = $comp.widgets?.[prop];
              if (pageWidget) {
                return pageWidget._userWidget;
              }

              return app?.__internal__?.$w?.[prop];
            },
          },
        ),
      },
      _dataBinds,
      compConfig,
      widgets,
      node,
      // lib: { const: constObj, tools: toolsObj },
      get props() {
        const { forwardRef, ...restProps } = props || {};
        return { ...restProps, events, data: propsData };
      },
      get state() {
        return state;
      },
      computed: {},
      get _instanceRef() {
        return ref;
      },
      get methods() {
        return ref.current?.methods;
      },
      set methods(value) {
        if (!ref.current) {
          ref.current = {};
        }
        ref.current.methods = value;
      },
    },
    {
      get: (obj, prop) => {
        return prop === '$WEAPPS_COMP' || prop === '$comp' ? obj : obj[prop];
      },
    },
  );

  return Object.assign($comp, {
    computed: createComputed(computed, $comp),
    handler: bindFuncObj(handler || {}, $comp),
  });
}
