import * as React from 'react';
import { ScopeContext, ForContext, getComponentRenderList } from './NodeRenderer';
import lodashSet from 'lodash.set';
import cloneDeep from 'lodash.clonedeep';
import { isScopeSlot, 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';

interface IContext {
  virtualFields: { [sourceKey: string]: React.FunctionComponent<any> };
  codeContext: any;
  scopeContext?: { [key: string]: any };
  dataContext?: any;
  updateContext?: any;
  injectContext?: {
    SlotRender?: React.FC<React.PropsWithChildren<{ componentSchema: any } & Record<string, any>>>;
  };
  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 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, dataContext, updateContext, injectContext, forContext } = 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 isHOC = isScopeSlot(componentSchema, key, injectContext);
      slots[key] = {
        type: isHOC ? 'HOC' : 'ELEMENT',
        node: isHOC
          ? (props) => {
              return (
                <InjectContext.Provider value={injectContext}>
                  <ScopeContext.Provider
                    value={{
                      ...scopeContext,
                      [componentSchema.id]: {
                        ...scopeContext?.[componentSchema.id],
                        [key]: props,
                      },
                    }}
                  >
                    <AppRender
                      key={key}
                      componentSchema={child as any}
                      renderSlot={options.renderSlot}
                      virtualFields={virtualFields}
                      codeContext={codeContext}
                      dataContext={dataContext}
                      updateContext={updateContext}
                    />
                  </ScopeContext.Provider>
                </InjectContext.Provider>
              );
            }
          : () => {
              return (
                <InjectContext.Provider value={injectContext}>
                  <ScopeContext.Provider value={scopeContext}>
                    <ForContext.Provider value={forContext}>
                      <AppRender
                        key={key}
                        componentSchema={child as any}
                        renderSlot={options?.renderSlot}
                        virtualFields={virtualFields}
                        codeContext={codeContext}
                        dataContext={dataContext}
                        updateContext={updateContext}
                      />
                    </ForContext.Provider>
                  </ScopeContext.Provider>
                </InjectContext.Provider>
              );
            },
      };
    }
  }

  return slots;
}

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

function getRenderList(
  props: IAppRenderProps & {
    key?: string;
    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,
  });
}

export const AppRender = observer((props: IAppRenderProps) => {
  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,
    scopeContext: React.useContext(ScopeContext),
    forContext: React.useContext(ForContext),
    injectContext: React.useContext(InjectContext),
  });
});
