import * as React from 'react';
import { observer } from 'mobx-react-lite';
import { emitEvent, checkVisible, isSlot } from '../utils';
import { get, set, isEqual, debounce } from 'lodash-es';
import { getDom } from '../utils/widgets';
import { getComponentChildren, generateSlotMetaMap } from './RenderWrapper';
import { InjectContext } from '../router';
import { ErrorBoundary } from '../utils/error';
import { processCSSProperties2Rem } from '@cloudbase/cals';

export const ForContext = React.createContext({});
export const ScopeContext = React.createContext({});

interface IComponentRendererProps {
  componentSchema?;
  rootNode?: boolean;
  virtualFields: {
    [sourceKey: string]: React.FunctionComponent<any>;
  };
  slots?: {
    [slotName: string]: {
      type: 'HOC' | 'ELEMENT';
      node: React.FunctionComponent | React.ReactElement;
    };
  };
  renderSlot?: boolean;
  genericComp?: {
    propName: any;
  };
  scopeContext?: { [key: string]: any };
  codeContext?: { [key: string]: any };
  emitEvents: string[];
  children: any;
  dataContext?: any;
  updateContext?: any;
}

function getSafeComponentProps({ style, classNameList }) {
  const componentProps: {
    className?: string;
    style?: React.CSSProperties;
  } = {};

  if (classNameList.length) {
    componentProps.className = classNameList.join(' ');
  }

  if (style && Object.keys(style).length) {
    componentProps.style = style;
  }

  return componentProps;
}

// TODO: 需要不断移除 dataBinds(style/classList)
function getBindData({
  widgetData,
  forItems,
  scopeContext,
  codeContext,
  commonStyle = {},
  classNameList = [],
}: {
  widgetData: any | any[];
  forItems: {
    [key: string]: any;
    forIndexes?: any[];
  };
  scopeContext: {
    [id: string]: any;
  };
  codeContext: any;
  commonStyle?: React.CSSProperties;
  classNameList?: string[];
}) {
  // bindData
  if (Array.isArray(widgetData)) {
    widgetData =
      forItems.forIndexes === void 0 || widgetData.length === 0
        ? {}
        : get(widgetData, getForIndexes(forItems, widgetData));
  }
  widgetData = widgetData || {};
  const fieldData = { ...widgetData };

  const isRootNode = widgetData && !widgetData.parent;
  const isInComposite = !!codeContext.$WEAPPS_COMP;

  // 再次计算 scope value
  for (let key in fieldData) {
    if (Object.prototype.hasOwnProperty.call(fieldData, key)) {
      const value = fieldData[key];
      if (value && value.__type === 'scopedValue') {
        try {
          fieldData[key] = value.getValue(scopeContext || {});
        } catch (e) {
          console.warn(`Error computing data bind '${key}' error:`, e);
          fieldData[key] = '';
        }
      }
    }
  }

  // bindStyle
  let bindStyle = fieldData.style || {};
  // 复合组件第一层需要将最外层样式 style 挂到节点上
  let cssStyle = commonStyle || {};
  if (isInComposite && isRootNode) {
    cssStyle = {
      ...cssStyle,
      ...(codeContext.$WEAPPS_COMP.props?.style || {}),
    };
    bindStyle = {
      ...bindStyle,
      ...(codeContext.$WEAPPS_COMP.props?.style || {}),
    };
  }
  const finalStyle = getFinalStyle(cssStyle, bindStyle);

  // bindClassList
  const bindClassList = fieldData.classList || [];
  const finalClassNameList = getFinalClassNameList(classNameList, bindClassList);

  // 当前在复合组件中，且当前节点为根节点
  if (isInComposite && isRootNode) {
    if (widgetData.ownerWidget) {
      Object.keys(widgetData.ownerWidget).forEach((propName) => {
        if (propName === 'role' || ['aria-', 'data-'].some((prefix) => propName.startsWith(prefix))) {
          widgetData[propName] = widgetData.ownerWidget[propName];
        }
      });
    }
  }

  // 防止渲染时 data 的 style 与实际的 style 冲突
  delete fieldData.style;

  return {
    fieldData,
    finalStyle,
    finalClassNameList: Array.from(new Set(finalClassNameList)),
  };
}

const _FOR_ERROR_CACHE_MAP = {};
function getForList(compId, dataBinds, parentForItems, dataContext) {
  /**
   * For循环渲染
   * @important
   * @default undfined // 代表没有进行for循环绑定
   */
  let forList = undefined;

  try {
    // 绑定了 for 变量，计算值最差应该是空数组
    // 没有绑定，那就是 undefined
    clearTimeout(_FOR_ERROR_CACHE_MAP[compId]);
    let list = dataBinds?._waFor ? dataBinds._waFor(parentForItems, undefined, dataContext) || [] : undefined;
    // 绑定了for 并且计算了值，但是不是数组类型，应该进行兜底
    if (list && !Array.isArray(list)) {
      _FOR_ERROR_CACHE_MAP[compId] = setTimeout(() => {
        console.warn(`${compId}循环绑定非数组值：`, list);
      }, 1000);
      list = [];
    }
    forList = list;
  } catch (e) {
    // 计算值出错则使用空数组兜底
    _FOR_ERROR_CACHE_MAP[compId] = setTimeout(() => {
      console.warn('_waFor data', e);
    }, 1000);
    forList = [];
  }

  if (forList) {
    if (!Array.isArray(forList)) {
      console.warn(`${compId}循环绑定非数组值：`, forList);
      forList = [];
    }
  }

  return forList;
}

// const FieldWrapper = React.memo(
//   _FieldWrapper,
//   (function () {
//     let latestRender = Date.now();
//     let latestTick = latestRender;
//     return (prevProps, nextProps) => {
//       const equal = isEqual(prevProps, nextProps);
//       const now = Date.now();
//       const delta = now - latestTick;
//       latestTick = now;
//       if (!equal && (delta > 10 || now - latestRender > 10)) {
//         latestRender = now;
//         return false;
//       }
//       return true;
//     };
//   })()
// );

const FieldWrapper = _FieldWrapper;

function _FieldWrapper({
  Field,
  componentSchema,
  id,
  data,
  style = {},
  className = '',
  events,
  compositeParent,
  context,
  children,
  updateContext,
  renderSlot = false,
}: {
  Field: React.FunctionComponent<any>;
  componentSchema: any;
  context: {
    virtualFields: any;
    forContext: any;
    forIndexes: any[];
    widgetsData: any;
    codeContext: any;
    dataContext?: any;
  };
  id: string;
  data: {
    [key: string]: any;
  };
  updateContext?: any;
  style?: React.CSSProperties;
  className?: string;
  events: string[];
  compositeParent: any;
  renderSlot?: boolean;
  children: any;
}) {
  const { forContext, forIndexes, widgetsData, codeContext, dataContext, virtualFields } = context;

  const injectContext = React.useContext(InjectContext) as any;
  const scopeContext = React.useContext(ScopeContext);
  const indexRef = React.useRef();
  const typeRef = React.useRef();
  const instanceRef = React.useRef();
  const isInComposite = !!codeContext.$WEAPPS_COMP;
  const watchedList = isInComposite
    ? [componentSchema]
    : [componentSchema, scopeContext, codeContext, forContext, dataContext, injectContext, virtualFields];

  // 多个组件的 slot 属性
  const slotMap = React.useMemo(() => {
    return generateSlotMap(
      generateSlotMetaMap(
        componentSchema,
        {
          scopeContext,
          forContext,
          dataContext,
          codeContext,
          virtualFields,
          updateContext,
          injectContext,
        },
        {
          renderSlot,
        },
      ),
    );
  }, watchedList);

  // 组件最终用于执行的事件函数
  const emit = React.useCallback(
    (trigger, listeners, eventData, forItems, domEvent, scopeContext) => {
      // 当组件是数据容器并且listeners未含有onDataChange事件时，增加一个onDataChange事件
      if (componentSchema?.extra?.compConfig?.isDataContainer) {
        listeners = listeners.concat({
          key: `wa${Date.now().toString().slice(-8)}`,
          trigger: 'onDataChange',
          isCapturePhase: false,
          data: {},
          dataBinds: {},
          instanceFunction: ({ event }) => {
            return updateContext(id, event?.detail?.data);
          },
        });
      }

      if (injectContext.emit) {
        return injectContext.emit(trigger, listeners, eventData, forItems, domEvent, scopeContext);
      } else {
        const event = {
          detail: eventData,
          name: trigger,
          target: widgetsData,
          currentTarget: widgetsData,
          domEvent,
        };
        forItems = {
          ...forItems,
          forIndexes: getForIndexes(forItems, widgetsData),
        };
        emitEvent(
          trigger,
          listeners,
          {
            event,
            customEventData: event,
            forItems,
            domEvent,
          },
          scopeContext,
          dataContext,
        );
      }
    },
    [widgetsData],
  );

  const currentWidget = Array.isArray(widgetsData) ? get(widgetsData, forIndexes) : widgetsData;

  let staticResourceAttribute: string[] = componentSchema.extra?.staticResourceAttribute || [];

  React.useLayoutEffect(() => {
    return () => {
      instanceRef.current = undefined;
    };
  }, []);

  React.useLayoutEffect(() => {
    if (currentWidget) {
      currentWidget._getInstanceRef = () => instanceRef;
    }
  }, [currentWidget, instanceRef]);

  if (!Array.isArray(staticResourceAttribute)) {
    staticResourceAttribute = [];
  }

  // 判断为容器组件，则增加一个onDataChange事件
  if (componentSchema?.extra?.compConfig?.isDataContainer && !events?.includes('onDataChange')) {
    events.push('onDataChange');
  }
  return (
    <ErrorBoundary
      FallbackComponent={({ error }) => {
        console.error(`组件${id}发生了错误，：${error}`);
        return <noscript />;
      }}
    >
      <Field
        ref={instanceRef}
        data={{
          ...data,
          ...slotMap,
          _selectableBlockEvents: {
            // 选区的预览的click事件
            onCustomEvent: ({ order: index, blockKey }) => {
              if (index !== undefined) {
                indexRef.current = index;
                typeRef.current = blockKey;
              }
            },
            events: componentSchema?.selectableBlock?.events.map((item) => item.name) || [],
            emit: (trigger, eventData, domEvent) =>
              emit(
                trigger,
                typeRef.current &&
                  indexRef.current !== undefined &&
                  data?.[typeRef.current as any]?.[indexRef.current as any]?.selectableBlock?.listenerInstances,
                eventData,
                forContext,
                domEvent,
                scopeContext,
              ),
          },
        }}
        id={id}
        emit={(trigger, eventData, domEvent) =>
          emit(trigger, componentSchema.listenerInstances, eventData, forContext, domEvent, scopeContext)
        }
        events={events}
        compositeParent={compositeParent}
        forIndexes={forIndexes}
        $node={currentWidget}
        domRef={setGetDomApi(currentWidget, { codeContext })}
        style={style}
        className={className}
        staticResourceAttribute={staticResourceAttribute}
      >
        {children}
      </Field>
    </ErrorBoundary>
  );
}

function generateSlotMap(slots, forContext?) {
  let map = {};
  Object.keys(slots).forEach((slotProp) => {
    let meta = slots[slotProp];
    if (meta.type === 'HOC') {
      set(map, slotProp, meta.node);
    } else {
      const Element = meta.node;
      if (forContext) {
        set(
          map,
          slotProp,
          <ForContext.Provider value={forContext}>
            <Element />
          </ForContext.Provider>,
        );
      } else {
        set(map, slotProp, <Element />);
      }
    }
  });
  return map;
}

function SlotWrapper(props: any) {
  return <>{props.children}</>;
}

export function getComponentRenderList(
  props: Omit<IComponentRendererProps, 'children'> & {
    key?: any;
    forContext: { [key: string]: any };
    injectContext: any;
    dataContext?: any;
    updateContext?: any;
  },
) {
  const {
    key,
    virtualFields,
    renderSlot,
    codeContext = {},
    scopeContext,
    genericComp = {},
    emitEvents = [],
    componentSchema = {},
    forContext = {},
    injectContext = {},
    rootNode = false,
    dataContext,
    updateContext,
  } = props;
  const compId = componentSchema.id;

  const isInComposite = !!codeContext.$WEAPPS_COMP;
  // 判断 widgets 是从 page 来的，还是组件来的
  const widgetsData = !isInComposite ? codeContext.$page.widgets[compId] : codeContext.$WEAPPS_COMP.widgets[compId];

  const classNameList = (componentSchema?.attributes?.class || '').split(/\s/).filter((item) => !!item);

  const dataBinds = codeContext._dataBinds?.[compId] || {};
  const FieldKey = genericComp['propName']
    ? codeContext.node[genericComp['propName']]
    : `${componentSchema.module}:${componentSchema.component}`;

  const Field = virtualFields[FieldKey];

  const context = {
    scopeContext,
    forContext,
    codeContext,
    dataContext,
  };

  if (isSlot(componentSchema)) {
    const SlotRender = !compId ? SlotWrapper : injectContext.SlotRender || SlotWrapper;

    const slotId = `${componentSchema?.extra?.__parent?.id}.${compId}`;
    return (
      <SlotRender component={slotId} componentSchema={componentSchema}>
        {getComponentChildren(componentSchema, {
          ...context,
          virtualFields,
          injectContext,
          updateContext,
        })}
      </SlotRender>
    );
  }

  // For循环渲染
  let forList = getForList(compId, dataBinds, forContext, dataContext);

  if (forList) {
    return forList
      .map((item, index) => {
        const forItemsIndexes = (forContext['forIndexes'] || []).concat(index);
        const forItems = {
          ...forContext,
          [compId]: item,
          index: { ...forContext.index, [compId]: index },
          forIndexes: forItemsIndexes,
        };
        const {
          fieldData: forItemData,
          finalStyle: forItemStyle,
          finalClassNameList: forItemClassNameList,
        } = getBindData({
          widgetData: widgetsData,
          forItems,
          scopeContext,
          codeContext,
          commonStyle: componentSchema.extra?.commonStyle,
          classNameList,
        });
        if (!(injectContext.checkVisible || checkVisible)(forItemData)) {
          return null;
        }

        return (
          <ForContext.Provider key={forItemData._key} value={forItems}>
            <FieldWrapper
              Field={Field}
              componentSchema={componentSchema}
              context={{
                ...context,
                virtualFields,
                widgetsData,
                forContext: forItems,
                forIndexes: forItemsIndexes,
              }}
              id={compId}
              updateContext={updateContext}
              data={{ ...forItemData }}
              {...getSafeComponentProps({
                style: forItemStyle,
                classNameList: forItemClassNameList,
              })}
              events={emitEvents}
              compositeParent={codeContext}
              renderSlot={renderSlot}
            >
              {getComponentChildren(componentSchema, {
                virtualFields,
                codeContext,
                scopeContext,
                forContext: forItems,
                injectContext,
                dataContext,
                updateContext,
              })}
            </FieldWrapper>
          </ForContext.Provider>
        );
      })
      .filter((item) => !!item);
  }

  // 修正 forIndexes
  const forIndexes = getForIndexes(forContext, widgetsData);

  // 单节点渲染
  const { fieldData, finalClassNameList, finalStyle } = getBindData({
    widgetData: widgetsData,
    forItems: forContext,
    scopeContext,
    codeContext,
    commonStyle: componentSchema.extra?.commonStyle,
    classNameList,
  });

  // false 或空字符串时
  if (!(injectContext.checkVisible || checkVisible)(fieldData)) {
    return [];
  }

  return [
    <FieldWrapper
      key={key}
      Field={Field}
      componentSchema={componentSchema}
      context={{
        virtualFields,
        forIndexes: forIndexes,
        forContext,
        widgetsData,
        codeContext,
        dataContext,
      }}
      id={compId}
      updateContext={updateContext}
      data={{ ...fieldData }}
      {...getSafeComponentProps({
        style: finalStyle,
        classNameList: finalClassNameList,
      })}
      events={emitEvents}
      compositeParent={codeContext}
      renderSlot={renderSlot}
    >
      {getComponentChildren(componentSchema, {
        virtualFields,
        codeContext,
        scopeContext,
        forContext,
        injectContext,
        dataContext,
        updateContext,
      })}
    </FieldWrapper>,
  ];
}

export const CompRenderer = observer(function (props: IComponentRendererProps) {
  return getComponentRenderList({
    ...props,
    scopeContext: React.createContext(ScopeContext),
    forContext: React.useContext(ForContext),
    injectContext: React.useContext(InjectContext),
  });
} as React.FunctionComponent<IComponentRendererProps>);

function getFinalStyle(commonStyle = {}, bindStyle = {}, widgetStyle = {}) {
  const remBindStyle = typeof bindStyle === 'object' ? processCSSProperties2Rem(bindStyle, { looseError: true }) : {};

  return {
    ...(processCSSProperties2Rem(commonStyle, { looseError: true }) || {}),
    ...(processCSSProperties2Rem(widgetStyle, { looseError: true }) || {}),
    ...remBindStyle,
  };
}

function getFinalClassNameList(classNameList = [], bindClassList = [], widgetClassList = []) {
  return [].concat(classNameList, bindClassList, widgetClassList);
}

// HACK: 从后向前保证循环的深度与 forIndexes 一致
// 后续需要将 For 循环迁移到外层
function getForIndexes(parentForItems, widgetsData) {
  return Array.isArray(widgetsData) && widgetsData.length > 0
    ? (parentForItems.forIndexes || []).slice(0 - getDeepArrLen(widgetsData))
    : undefined;
}

function getDeepArrLen(arr, len = 0) {
  if (Array.isArray(arr)) {
    return getDeepArrLen(arr[0], len + 1);
  } else {
    return len;
  }
}

function setGetDomApi(currentWidget, { codeContext }) {
  if (!currentWidget) return React.createRef();
  const isComposite = !currentWidget.widgetType.startsWith('gsd-h5-react');
  const isInComposite = !!codeContext?.$WEAPPS_COMP;

  // 如果当前是复合组件，不做 getDom 的挂载
  if (!isComposite) {
    if (!currentWidget.domRef) {
      currentWidget.domRef = React.createRef();
    }
    if (!currentWidget.getDom) {
      currentWidget.getDom = (options) => getDom(currentWidget.domRef.current, options);
    }

    if (
      isInComposite && // 当前在复合组件内
      !currentWidget.parent && // 当前节点为复合组件的根节点
      codeContext.node && // 复合组件的 node 已经创建
      !codeContext.node.getDom // 复合组件的 node 未挂载 getDom 方法
    ) {
      codeContext.node.domRef = currentWidget.domRef;
      codeContext.node.getDom = currentWidget.getDom;
    }
  }

  return currentWidget.domRef;
}
