import * as React from 'react';
import { ScopeContext, ForContext, getComponentRenderList, DataContext } from './NodeRenderer';
import { getScopeSlotMeta, isSlot } from '../utils/common';
import { IBasicComponent, IComplexComponent } from '@cloudbase/cals';
import { observer } from 'mobx-react-lite';
import { ComponentNotFound } from '../utils/error';
import { InjectContext } from '../router';
import { checkVisible } from '../utils';
import { get, isEqual } from 'lodash-es';
// import { trace } from 'mobx';

export interface IContext {
  virtualFields: { [sourceKey: string]: React.FunctionComponent<any> };
  codeContext: {
    $page?: any;
    /**
     * 复合组件内部实例对象
     * 有则代表为生成组件
     */
    $WEAPPS_COMP?: any;
    _dataBinds?: Record<string, Function>;
    node?: any;
  };
  scopeContext?: { [key: string]: any };
  dataContext?: any;
  updateContext?: any;
  injectContext?: {
    VisiblePlaceHolder?: React.FunctionComponent<{ id: string }>;
    processCssUnit?: 'px' | 'rpx' | 'rem';
    checkVisible?: () => boolean;
    SlotRender?: React.FC<React.PropsWithChildren<{ componentSchema: any } & Record<string, any>>>;
    checkDisplay?: (data) => boolean;
  };
  forContext?: IForContext;
}

interface IForContext {
  [id: string]: any;
}
function checkDoNotRenderSlot(componentSchema, renderSlot, rootNode) {
  // 判断是否为 slot
  return isSlot(componentSchema) && !(renderSlot || rootNode);
}

export function getComponentChildren(
  component,
  context: {
    virtualFields: Record<string, React.FunctionComponent>;
    codeContext: any;
    scopeContext: Record<string, any>;
    forContext: IForContext;
    injectContext: Record<string, any>;
    dataContext: Record<string, any>;
    updateContext: (id: string, data: any) => void;
  },
) {
  const { items = [] } = component;
  if (!items.length) {
    return [];
  }
  const list = items.sort((a, b) => (a.extra?.xIndex || 0) - (b.extra?.xIndex || 0));

  const { virtualFields, codeContext, scopeContext, forContext, injectContext, dataContext, updateContext } = context;

  return list.map((schema) => {
    return getRenderList({
      key: schema.id,
      componentSchema: schema,
      rootNode: false,
      renderSlot: false,
      virtualFields,
      codeContext,
      scopeContext,
      forContext,
      injectContext,
      dataContext,
      updateContext,
    });
  });
}

export const Trace = observer(function Trace({ id }: any) {
  const scopeContext = React.useContext(ScopeContext);
  console.log('id', id, get(scopeContext, id));
  return null;
});

function useEqualValue(value) {
  const valueRef = React.useRef(value);

  return React.useMemo(() => {
    if (!isEqual(value, valueRef.current)) {
      valueRef.current = value;
    }
    return valueRef.current;
  }, [value]);
}

function HOCWtichRegistData(
  props: {
    id: string;
    data: any;
    componentSchema: any;
    renderSlot: boolean;
    slotConfig?: {
      'x-slot-directives'?: {
        for: string;
        _index: string;
      };
    };
  } & IContext,
) {
  const {
    id,
    data: _data,
    componentSchema,
    renderSlot,
    slotConfig = {},
    codeContext,
    forContext: _forContext,
    scopeContext,
    injectContext,
    dataContext,
    virtualFields,
  } = props;
  const data = useEqualValue(_data);
  const isMultiSlotInstance = slotConfig?.['x-slot-directives']?._index && slotConfig?.['x-slot-directives']?.for;
  const indexKey = slotConfig?.['x-slot-directives']?._index;

  const isInComposite = !!codeContext.$WEAPPS_COMP;
  const widgetsData = !isInComposite ? codeContext.$page.widgets[id] : codeContext.$WEAPPS_COMP.widgets[id];

  let forContext = React.useMemo(() => {
    const { forIndexes = [], forStack = [] } = _forContext;
    return isMultiSlotInstance && data[indexKey] !== undefined
      ? {
          ..._forContext,
          [id]: data,
          forIndexes: forIndexes.concat(data[indexKey]),
          forStack: [
            ...forStack,
            {
              currentIndex: data[indexKey],
              currentItem: data,
            },
          ],
        }
      : _forContext;
  }, [data[indexKey]]);

  const { forIndexes = [] } = forContext;

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

  const [componentId, key] = id.split('.');

  React.useEffect(() => {
    if (!currentWidget) {
      // debugger;
      return;
    }
    currentWidget._scope.scopeContext = data;
  }, [currentWidget, data]);

  const memoScopeContext = React.useMemo(() => {
    return {
      ...scopeContext,
      [componentId]: {
        [key]: data,
      },
    };
  }, [componentId, key, data]);

  return currentWidget ? (
    <InjectContext.Provider value={injectContext}>
      <ScopeContext.Provider value={memoScopeContext}>
        <DataContext.Provider value={dataContext}>
          <ForContext.Provider value={forContext}>
            {/* <Trace id={id} /> */}
            <AppRender
              componentSchema={componentSchema}
              renderSlot={renderSlot}
              virtualFields={virtualFields}
              codeContext={codeContext}
            />
          </ForContext.Provider>
        </DataContext.Provider>
      </ScopeContext.Provider>
    </InjectContext.Provider>
  ) : null;
}

export function generateSlotMetaMap(
  componentSchema,
  context: IContext,
  options?: {
    rootNode?: boolean;
    renderSlot: boolean;
  },
): {
  [key: string]:
    | {
        type: 'HOC';
        node: React.FunctionComponent;
      }
    | {
        type: 'ELEMENT';
        node: React.ReactElement;
      };
} {
  const { items = [] } = componentSchema;
  const { scopeContext, codeContext, virtualFields, injectContext } = context;

  const slots = {};
  // eslint-disable-next-line guard-for-in
  for (const child of items) {
    // TODO 做到不依赖组件 id
    const key = child.id;

    if (isSlot(child)) {
      const slotId = `${componentSchema?.id}.${key}`;
      if ((injectContext.checkVisible || checkVisible)({ id: slotId })) {
        const { isScopeSlot: isHOC, config } = getScopeSlotMeta(componentSchema, key, injectContext);
        slots[key] = {
          type: isHOC ? 'HOC' : 'ELEMENT',
          node: isHOC
            ? function ({ forContext, dataContext }) {
                return function ScopedSlot(props: any) {
                  return (
                    <HOCWtichRegistData
                      id={slotId}
                      data={props}
                      componentSchema={child}
                      renderSlot={options.renderSlot}
                      slotConfig={config}
                      codeContext={codeContext}
                      forContext={forContext}
                      dataContext={dataContext}
                      scopeContext={scopeContext}
                      injectContext={injectContext}
                      virtualFields={virtualFields}
                    />
                  );
                };
              }
            : ({ forContext, dataContext }) => {
                return (
                  <InjectContext.Provider value={injectContext}>
                    <ScopeContext.Provider value={scopeContext}>
                      <DataContext.Provider value={dataContext}>
                        <ForContext.Provider value={forContext}>
                          <AppRender
                            componentSchema={child}
                            renderSlot={options?.renderSlot}
                            virtualFields={virtualFields}
                            codeContext={codeContext}
                          />
                        </ForContext.Provider>
                      </DataContext.Provider>
                    </ScopeContext.Provider>
                  </InjectContext.Provider>
                );
              },
        };
      }
    }
  }

  return slots;
}

interface IAppRenderProps extends IContext {
  className?: string;
  style?: React.CSSProperties;
  componentSchema: (IBasicComponent | IComplexComponent) & {
    extra?: { emitEvents: [] };
  };
  renderSlot?: boolean;
  rootNode?: boolean;
  updateContext?: any;
  updateKey?: any;
}

function getRenderList(
  props: IAppRenderProps & {
    key?: string;
    dataContext?: Record<string, { data?: any }>;
    forContext: IForContext;
    injectContext: Record<string, any>;
  },
) {
  const {
    key,
    virtualFields,
    componentSchema,
    renderSlot,
    rootNode = true,
    codeContext,
    scopeContext,
    forContext = {},
    injectContext = {},
    dataContext,
    updateContext,
  } = props;

  const { genericComp = {} } = (componentSchema.extra as any) || {};

  // 判断是否为 slot
  if (checkDoNotRenderSlot(componentSchema, renderSlot, rootNode)) {
    return null;
  }

  if (componentSchema.module && componentSchema.component) {
    const sourceKey = `${componentSchema.module}:${componentSchema.component}`;
    const Field = virtualFields[sourceKey];
    if (!Field) {
      return <ComponentNotFound key={key} module={componentSchema.module} component={componentSchema.component} />;
    }
  }

  return getComponentRenderList({
    key,
    componentSchema,
    rootNode,
    emitEvents: componentSchema.extra?.emitEvents || [],
    renderSlot,
    virtualFields,
    codeContext,
    scopeContext,
    forContext,
    dataContext,
    updateContext,
    injectContext,
    genericComp,
  });
}
// CommonAppRender.whyDidYouRender = true;
export function CommonAppRender(props: IAppRenderProps) {
  // trace(true);
  const { className, componentSchema } = props;
  //  wrapperClass;
  const preClassNameList = React.useRef<string[]>([]);
  const { items = [] } = componentSchema;
  const currentInjectClassNameList = className?.split?.(/\s/)?.filter?.((item) => !!item) || [];

  const containerEl = items[0];
  if (className && containerEl) {
    if (!containerEl.attributes) {
      containerEl.attributes = {};
    }
    let classNameList = (containerEl.attributes?.class || '').split(/\s/).filter((item) => !!item);

    // 先替换掉先前计算出来的className部分
    if (preClassNameList.current.length) {
      const map = preClassNameList.current.reduce((map, item) => {
        map[item] = true;
        return map;
      }, {});

      classNameList = classNameList.filter((item) => !map[item]);
    }

    containerEl.attributes.class = Array.from(new Set(classNameList.concat(currentInjectClassNameList))).join(' ');
  }

  preClassNameList.current = currentInjectClassNameList;

  return getRenderList({
    ...props,
    // dataContext: {},
    // scopeContext: {},
    // forContext: {},
    // injectContext: {},
    dataContext: React.useContext(DataContext),
    scopeContext: React.useContext(ScopeContext),
    forContext: React.useContext(ForContext),
    injectContext: React.useContext(InjectContext),
  });
}

export const AppRender = observer(CommonAppRender as React.FunctionComponent<IAppRenderProps>);
