'use strict';
import { generateForContextOfWidget, generateWidgetAPIContext, getWidget } from './widget';
import { observable, untracked } from 'mobx';
import { getAccessToken, loginScope } from '../datasources/index';
import { app } from '../app/weapps-api';

/**
 * Convert abcWordSnd -> abc-word-snd
 */
export function toDash(str) {
  return str.replace(/[A-Z]/g, (upperLetter) => `-${upperLetter.toLowerCase()}`);
}

export function createComputed(funcs, bindContext = null) {
  const computed = {};
  for (const name in funcs) {
    Object.defineProperty(computed, name, {
      get() {
        try {
          return bindContext ? funcs[name].call(bindContext) : funcs[name]();
        } catch (e) {
          console.error('Computed error', e);
        }
      },
      enumerable: true,
    });
  }
  return computed;
}

export function generateDataContext(widget) {
  const dataContext = {};
  while (widget?._scope) {
    const current = widget;
    // 此处采用代理的方式，是为了可以获取到最新的 _scope.dataContext 防止 dataContext 引用被重新赋值
    Object.defineProperty(dataContext, current._scope.id, {
      get() {
        return current._scope.dataContext;
      },
    });
    widget = widget.parent;
  }
  return dataContext;
}

/**
 * @param evtListeners
 * @param [options]
 */
export function createEventHandlers(
  evtListeners,
  options = {
    looseError: false,
  },
) {
  const { looseError = false } = options;
  const evtHandlers = {};
  for (const name in evtListeners) {
    const listeners = evtListeners[name];
    evtHandlers[name] = function (event = {}) {
      const self = this;
      const owner = this._getInstance();
      const target = !!event?.target?.id ? getWidget(owner.widgets, event.target.id) : undefined;
      const currentTarget = !!event?.currentTarget?.id ? getWidget(owner.widgets, event.currentTarget.id) : undefined;
      event.target = target;
      event.currentTarget = currentTarget;
      const [prefix = ''] = name.split('$');
      // The page event handler
      const forContext = (!!currentTarget && generateForContextOfWidget(currentTarget)) || {};
      const { lists = [], forItems = {} } = forContext;
      const dataContext = untracked(() => generateDataContext(currentTarget));
      const $w = untracked(() => generateWidgetAPIContext(owner?.__internal__?.$w, currentTarget, forContext));

      listeners.forEach(async (l) => {
        let { data = {}, boundData = {} } = l;
        data = { ...data };
        try {
          for (const k in boundData) {
            set(data, k, boundData[k].call(owner, owner, lists, forItems, event, dataContext, $w));
          }
          let res = await l.handler.call(owner, { event, data });
          let eventName = prefix && l.key ? `${prefix}$${l.key}_success` : '';
          self[eventName] &&
            self[eventName]({
              ...event,
              detail: res,
            });
        } catch (e) {
          let eventName = l.key ? `${prefix}$${l.key}_fail` : '';
          if (self[eventName]) {
            console.error(`事件响应失败`, e);
            await self[eventName]({
              ...event,
              detail: e,
            });
          } else {
            if (!looseError) {
              app.showToast({
                icon: 'error',
                title: `事件响应失败`,
              });
            }
            throw e;
          }
        }
      });
    };
  }

  return evtHandlers;
}
export function getDeep(target, key, keySeparator = '.') {
  if (key == null) {
    return target;
  }
  const keys = (key + '').split(keySeparator);
  while (keys.length > 0 && target != null) {
    target = target[keys.shift()];
  }
  return keys.length === 0 ? target : undefined;
}

/**
 * Touch all props of given object deeply
 */
export function touchObj(obj) {
  if (!obj) {
    return;
  }
  if (typeof obj === 'string') {
    return;
  }
  if (Array.isArray(obj)) {
    obj.forEach(touchObj);
  } else if (obj) {
    Object.keys(obj).forEach((key) => touchObj(obj[key]));
  }
}

export function throttle(fn, limit) {
  let lastExecTime = 0;
  let timer = null;

  function invoke() {
    lastExecTime = Date.now();
    timer = null;
    fn();
  }

  const throttled = function () {
    const idledDuration = Date.now() - lastExecTime;
    if (idledDuration >= limit) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      invoke();
    } else if (!timer) {
      timer = setTimeout(invoke, limit - idledDuration);
    }
  };
  return throttled;
}

export function deepEqual(a, b) {
  if (a === b) {
    return true;
  }

  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) {
      return false;
    }
    for (let i = 0; i < a.length; i++) {
      if (!deepEqual(a[i], b[i])) {
        return false;
      }
    }
    return true;
  }

  if (a && b && typeof a === 'object' && typeof b === 'object') {
    const aProps = Object.keys(a),
      bProps = Object.keys(b);
    if (!deepEqual(aProps, bProps)) {
      return false;
    }
    for (let i = 0; i < aProps.length; i++) {
      const prop = aProps[i];
      if (!deepEqual(a[prop], b[prop])) {
        return false;
      }
    }
    return true;
  }
  return false;
}

function isObject(value) {
  var type = typeof value;
  return !!value && (type == 'object' || type == 'function');
}

function isIndex(value, length) {
  length = length == null ? 9007199254740991 : length;
  return (
    !!length &&
    (typeof value == 'number' || /^(?:0|[1-9]\d*)$/.test(value)) &&
    value > -1 &&
    value % 1 == 0 &&
    value < length
  );
}

function assignValue(object, key, value) {
  var objValue = object[key];
  if (
    !(Object.hasOwnProperty.call(object, key) && (objValue === value || (objValue !== objValue && value !== value))) ||
    (value === undefined && !(key in object))
  ) {
    object[key] = value;
  }
}

export function set(object, path, value) {
  if (!isObject(object)) {
    return object;
  }
  path = path.split('.');

  var index = -1,
    length = path.length,
    lastIndex = length - 1,
    nested = object;

  while (nested != null && ++index < length) {
    var key = path[index],
      newValue = value;

    if (index != lastIndex) {
      var objValue = nested[key];
      newValue = undefined;
      if (newValue === undefined) {
        newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
      }
    }
    assignValue(nested, key, newValue);
    nested = nested[key];
  }
  return object;
}

export function findLoginPage() {
  const { app } = getApp();
  const { pages = [] } = app.__internal__.getConfig();
  return pages.find((item) => item.type === 'login');
}

let _AUTH_CONFIG_CACHE = null;

export async function getAuthConfig() {
  const { app } = getApp();
  if (_AUTH_CONFIG_CACHE) {
    return _AUTH_CONFIG_CACHE;
  }
  try {
    const res = await app.cloud.callWedaApi({
      action: 'DescribeRuntimeResourceStrategy',
      data: {
        ResourceType: `<%= isAdminPortal? 'modelApp' : 'app'%>`,
        ResourceId: app.id,
      },
    });
    const settingData = {};
    // 云api不支持map只能传字符串，需要转换
    res.forEach((item) => {
      settingData[item.Key] = ['AllowRegister', 'NeedLogin'].includes(item.Key) ? item.Value === '1' : item.Value;
    });
    _AUTH_CONFIG_CACHE = settingData;
    return _AUTH_CONFIG_CACHE;
  } catch (e) {
    return {
      NeedLogin: false,
      RejectStrategy: 'show_warning',
    };
  }
}

let _AUTH_CACHE_MAP = {};

async function getAccessPermission(app, appId, pageId) {
  const cacheKey = `${appId}-${pageId}`;
  if (_AUTH_CACHE_MAP[cacheKey] !== undefined) {
    return _AUTH_CACHE_MAP[cacheKey];
  }

  const res = await app.cloud.callWedaApi({
    action: 'DescribeResourcesPermission',
    data: {
      ResourceType: `<%= isAdminPortal? 'modelApp' : 'app'%>`,
      ResourceIdList: [cacheKey],
      AppResourceId: appId,
    },
  });
  if (Array.isArray(res)) {
    const resourceInfo = res.find((i) => i.ResourceId === cacheKey);
    const isAccess = !!resourceInfo?.IsAccess;
    _AUTH_CACHE_MAP[cacheKey] = isAccess;
    return isAccess;
  } else {
    throw new Error(`鉴权接口返回参数不正确 - ${JSON.stringify(res)}`);
  }
}

export async function checkAnonymous() {
  let isAnonymous = true;
  try {
    const { accessToken } = await getAccessToken();
    isAnonymous = !accessToken;
    const { app } = getApp();
    // 私密链路有token则不是匿名; tcb链路会匿名登录需要多验证scope === 'anonymous'
    if (accessToken && app.__internal__.getCloudSdkConfig('endpointType') === 'tcb-api') {
      const scope = await loginScope();
      isAnonymous = scope === 'anonymous';
    }
  } catch (e) { }
  return isAnonymous;
}

export function formatErrorMsg(e) {
  let msg = e?.errMsg || e?.error_description || e?.message;
  const uri = e?.error_uri;
  msg = `${msg}${uri ? '-' + uri : ''}`;
  if (msg.startsWith('request:fail')) {
    msg = `网络故障, 请检查本地网络连接是否正常(${msg})`;
  }
  return msg;
}

/**
 * 检查页面权限
 **/
export async function checkAuth(app, appId, $page) {
  const loginPage = findLoginPage(app);
  if (loginPage?.id === $page.id) {
    return true;
  }
  app.showNavigationBarLoading();
  const requestList = [getAccessPermission(app, appId, $page.id)];
  // 暂时先认为有登录页则自定义登录功能开启且生效
  if (loginPage) {
    requestList.push(getAuthConfig(app));
  }
  try {
    const [isAccess, authConfig] = await Promise.all(requestList);
    app.hideNavigationBarLoading();

    const isAnonymousUser = await checkAnonymous();

    if (!isAccess) {
      if (isAnonymousUser && loginPage && (authConfig.NeedLogin || authConfig.RejectStrategy == 'to_login')) {
        redirectToLogin($page);
      } else {
        app.showToast({
          title: '页面无访问权限',
          icon: 'error',
        });
      }
    } else if (loginPage && authConfig.NeedLogin) {
      // 此分支逻辑本不应该前端判断是否登录，历史原因后端短期内搞不定，后续后端优化后删除
      try {
        if (isAnonymousUser) {
          redirectToLogin($page);
          return false
        }
      } catch (e) {
        redirectToLogin($page);
        return false
      }
    }
    return isAccess;
  } catch (e) {
    app.showModal({
      title: '页面鉴权失败',
      content: formatErrorMsg(e),
      showCancel: false,
    });
    return false;
  }
}

export function redirectToLogin(currentPage) {
  // 去登录则清空权限缓存。
  _AUTH_CACHE_MAP = {};
  const { app } = getApp();
  const loginPage = findLoginPage(app);
  if (!currentPage) {
    currentPage = app.utils.getCurrentPage() || {};
  }
  if (loginPage?.id === currentPage.id) {
    return true;
  }
  if (loginPage) {
    app.redirectTo({
      pageId: loginPage.id,
      params: {
        sourcePageId: currentPage.id,
        sourcePageParams: currentPage.params,
      },
    });
  } else {
    app.showToast({
      title: '用户未登录',
      icon: 'error',
    });
  }
}

let loading = {};
export let enumOptions = observable({});

export function formatEnum(path, optionname) {
  // 判断是单选还是多选
  let isSingle = Array.isArray(path);
  // 获取到options
  let parseOptions = 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(',');
}

function getEnumOptions(optionName) {
  if (enumOptions[optionName]) {
    return enumOptions[optionName];
  }
  if (!loading[optionName]) {
    loading[optionName] = true;
    getGeneralOptions(optionName).then((data) => {
      enumOptions[optionName] = data?.Items[0]?.Config;
    });
  }
  return '';
}

async function getGeneralOptions(optionName) {
  const { app } = getApp();
  return app.cloud.callWedaApi({
    action: 'DescribeGeneralOptionsDetailList',
    data: {
      PageSize: 1,
      PageIndex: 1,
      OptNameList: [optionName],
    },
  });
}

export function getMpEventHandlerName(widgetId, evtName, modifier = {}) {
  // Only builtin events have will bubble
  return `on${widgetId}$${evtName.replace(/\./g, '_')}${modifier.isCapturePhase ? '$cap' : ''}${modifier.noPropagation ? '$cat' : ''
    }`;
}

function isPlainObject(src) {
  return Object.prototype.toString.call(src) === '[object Object]';
}

const isEmptyObj = (obj) => {
  if (!isPlainObject(obj)) {
    return false;
  }
  for (const i in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, i)) {
      return false;
    }
  }
  return true;
};
