import differencehash from 'lodash/difference';
import omit from 'lodash/omit';
import React, { Profiler, StrictMode } from 'react';
import LazyComponent from './components/LazyComponent';
import {
  filterSchema,
  loadRenderer,
  RendererComponent,
  RendererConfig,
  RendererEnv,
  RendererProps,
  resolveRenderer
} from './factory';

import { asFormItem } from './renderers/Form/Item';
import { renderChild, renderChildren } from './Root';
import { IScopedContext, ScopedContext } from './Scoped';
import { Schema, SchemaNode } from './types';
import { DebugWrapper, enableAMISDebug } from './utils/debug';
import getExprProperties from './utils/filter-schema';
import { anyChanged, chainEvents, autobind, difference, getChangedProp } from './utils/helper';
import { RendererEvent } from './utils/renderer-event';
import { SimpleMap } from './utils/SimpleMap';
import { isEqual } from 'lodash';

interface SchemaRendererProps extends Partial<RendererProps> {
  schema: Schema;
  $path: string;




  env: RendererEnv;
}

interface BroadcastCmptProps extends RendererProps {
  component: RendererComponent;
}

const defaultOmitList = [
  'type',
  'name',
  '$ref',
  'className',
  'data',
  'children',
  'ref',
  'visible',
  'visibleOn',
  'hidden',
  'hiddenOn',
  'disabled',
  'disabledOn',
  'component',
  'detectField',
  'defaultValue',
  'defaultData',
  'required',
  'requiredOn',
  'syncSuperStore',
  'mode',
  'body'
];

const componentCache: SimpleMap = new SimpleMap();

class BroadcastCmpt extends React.Component<BroadcastCmptProps> {
  ref: any;
  unbindEvent: (() => void) | undefined = undefined;
  static contextType = ScopedContext;

  constructor(props: BroadcastCmptProps, context: IScopedContext) {
    super(props);
    this.triggerEvent = this.triggerEvent.bind(this);
  }

  componentDidMount() {
    const { env } = this.props;
    this.unbindEvent = env.bindEvent(this.ref);
  }

  componentWillUnmount() {
    this.unbindEvent?.();
    this.ref = null // 解绑定
  }

  getWrappedInstance() {
    return this.ref;
  }

  async triggerEvent(
    e: React.MouseEvent<any>,
    data: any
  ): Promise<RendererEvent<any> | undefined> {
    return await this.props.env.dispatchEvent(e, this.ref, this.context, data);
  }

  @autobind
  childRef(ref: any) {
    while (ref && ref.getWrappedInstance) {
      ref = ref.getWrappedInstance();
    }

    this.ref = ref;
  }

  render() {

    const { component: Component, ...rest } = this.props;

    const isClassComponent = Component.prototype?.isReactComponent;

    // 函数组件不支持 ref https://reactjs.org/docs/refs-and-the-dom.html#refs-and-function-components

    return isClassComponent ? (
      <Component
        ref={this.childRef}
        {...rest}
        dispatchEvent={this.triggerEvent}
      />
    ) : (
      <Component {...rest} dispatchEvent={this.triggerEvent} />
    );
  }
}

let renderCount = 0
let renderCountTimer: any
export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
  static displayName: string = 'Renderer';

  rendererKey = '';
  renderer: RendererConfig | null;
  ref: any;

  schema: any;
  path: string;

  constructor(props: SchemaRendererProps) {
    super(props);
    this.refFn = this.refFn.bind(this);
    this.renderChild = this.renderChild.bind(this);
    this.reRender = this.reRender.bind(this);
    this.resolveRenderer(this.props);
  }

  componentWillUnmount(): void {
    this.ref = null
  }

  // 限制：只有 schema 除外的 props 变化，或者 schema 里面的某个成员值发生变化才更新。
  shouldComponentUpdate(nextProps: SchemaRendererProps) {
    // if (!renderCountTimer) clearTimeout(renderCountTimer)
    // renderCountTimer = setTimeout(() => {
    //   renderCount = 0
    //   clearTimeout(renderCountTimer)
    //   renderCountTimer = null
    // }, 1000)

    const schemaType = this.props.schema.type;
    if (nextProps.schema.type !== schemaType) {
      return true
    }
    // 判断一下，若是表格或者单元格，则数据没变化，则不更新
    if (schemaType === 'cell'
    ) {
      const cellOriStype = this.props.style;
      const cellNextStype = nextProps.style;
      // 一键列宽功能
      if (this.props.autoWidth !== nextProps.autoWidth) {
        return true
      }
      if (this.props.tableRotate !== nextProps.tableRotate) return true
      if (anyChanged(['left', 'position', 'right', 'zIndex', 'top'], cellOriStype, cellNextStype)) {
        return true;
      }
      if (this.props.className != nextProps.className) {
        return true;
      }
      const originColWidth = this.props.schema.column.width;
      const nextColWidth = nextProps.schema.column.width;
      if (originColWidth != nextColWidth) {
        return true
      }
      // 超级表头删除头部时会数据错乱，就更新有超级表头的
      const groupDisabled = this.props.schema.groupName ? true : false;
      if (groupDisabled) {
        return true;
      }

      const cellOriType = this.props.schema.column.type;
      if (cellOriType === 'operation') {
        const operationCol = this.props.fold !== nextProps.fold;
        if (operationCol) {
          return true
        }
        return !isEqual(this.props.data as any, nextProps.data as any)
      } else {
        const isNoDiff = (this.props.value === nextProps.value) || (this.props.value == nextProps.value);
        if (this.props.schema.name !== nextProps.schema.name) {
          return true;
        }
        if (this.props?.schema?.linkId) {
          if (anyChanged(['data'], this.props, nextProps)) {
            return true
          }
        }
        if (isNoDiff) {
          const quickexist = this.props.schema.quickEdit;
          if (quickexist) {
            return !isEqual(this.props.data as any, nextProps.data as any)
          }
          return false;
        } else {
          return true;
        }
      }
    }
    if (schemaType === 'table'
    ) {
      if (this.props.autoWidth !== nextProps.autoWidth) {
        return true
      }
      const schemaOriType = this.props.schema.$schema?.type;
      //解决tabs一次性加载时，后面的table高度计算时获取不到在dom中的定位导致高度计算错误
      if (this.props.tabsdefer !== nextProps.tabsdefer) {
        return true
      }
      if (this.props.showColumnsFilter !== nextProps.showColumnsFilter) {
        return true
      }
      if (schemaOriType === 'crud') {
        const operationCol = this.props.foldColumns !== nextProps.foldColumns;
        if (operationCol) {
          return true
        }
        if (this.props.tableRotate != nextProps.tableRotate) {
          return true;
        }
        if (this.props.loadmoreLoading != nextProps.loadmoreLoading) {
          return true;
        }
        const propsItems = this.props.data?.items;
        const nextPropsItems = nextProps.data?.items;

        const isNoDiff = (propsItems === nextPropsItems) || (propsItems == nextPropsItems);
        if (isNoDiff && this.props.data?.items?.length !== 0) {
          return false;
        } else {
          return true;
        }
      }
    }

    const props = this.props;
    const propsDiff: Array<string> = differencehash(Object.keys(props), ['schema', 'scope']);
    const nextPropsDiff: Array<string> = differencehash(Object.keys(nextProps), ['schema', 'scope']);

    const changeProps = getChangedProp(nextPropsDiff, props, nextProps)
    if (
      propsDiff.length !== nextPropsDiff.length || changeProps.length
    ) {


      // // 文本类型不影响渲染的属性-根据对应的组件得出
      // const notChangeContainerType = ["formPristine", "userChange"]


      // // 外部容器类型的组件，仅有data变化 不重新渲染 或者style没有具体变化
      // if (!changeProps.some(_ => !([...notChangeContainerType, "data"].includes(_.key) || (_.key === 'style' && JSON.stringify(_.fromValue) === JSON.stringify(_.toValue)))) && ['drawer', 'dialog'].includes(schemaType)) {
      //   return false
      // }

      // // 外部容器类型的组件，仅有data变化 不重新渲染 或者style没有具体变化
      // if (!changeProps.some(_ => !(notChangeContainerType.includes(_.key) || (_.key === 'style' && JSON.stringify(_.fromValue) === JSON.stringify(_.toValue)))) && ['crud', 'form'].includes(schemaType)) {
      //   return false
      // }

      // // 文本类型不影响渲染的属性-根据对应的组件得出
      // const notChangeType = ['itemsRaw', 'loadHasMore', 'query', 'orders', 'onAction', 'userChange']

      // // 文本类型 'itemsRaw', 'loadHasMore', 'query', 'orders',  'onAction', "$schema" 完全不影响业务 跳过渲染
      // if (!changeProps.some(_ => !([...notChangeType, 'data'].includes(_.key) || (_.key === 'style' && JSON.stringify(_.fromValue) === JSON.stringify(_.toValue)))) && ['button', 'pagination', 'date', 'input-text', 'input-number', 'theme'].includes(schemaType))
      //   return false

      // // 文本类型 '
      // if (!changeProps.some(_ => !(notChangeType.includes(_.key) || (_.key === 'style' && JSON.stringify(_.fromValue) === JSON.stringify(_.toValue)))) && ['action', 'service', 'tpl', 'lion-tpl'].includes(schemaType))
      //   return false


      // if (changeProps.length) {
      //   console.log('属性变化触发渲染', schemaType, '变化属性', changeProps, '组件实例', this)
      // }

      return true;
    } else {
      const nextPropsSchemaList: Array<string> = Object.keys(nextProps.schema);
      const propsSchemaList: Array<string> = Object.keys(props.schema);
      if (
        propsSchemaList.length !== nextPropsSchemaList.length || anyChanged(nextPropsSchemaList, props.schema, nextProps.schema)
      ) {
        return true;
      }
    }

    return false;
  }

  // Jay
  // 设置初始时 hidden 或 hiddenOn 就为 true 的input-table对应在数据域里的数据为{}，
  // 因为初始时 hidden 或 hiddenOn 就为 true的组件不会渲染，所以数据不会经过input-table处理
  // 因此在这里处理下初始时隐藏的input-table
  componentDidUpdate() {
    const { schema, ...rest } = this.props
    if ((schema.type === 'input-table' || schema.type === 'input-table-field') && !rest.store?.hiddenIputTable[schema.name] && rest.store?.data[schema.name]) {
      const detectData = rest.data
      const exprProps: any = detectData
        ? getExprProperties(schema, detectData, undefined, rest)
        : {};
      if (exprProps?.hidden || exprProps?.visible === false) {
        rest.store?.updateHiddenInputTable({ [schema.name]: {} })
      }
    }
  }

  resolveRenderer(props: SchemaRendererProps, force = false): any {
    let schema = props.schema;
    let path = props.$path;

    if (schema && schema.$ref) {
      schema = {
        ...props.resolveDefinitions(schema.$ref),
        ...schema
      };

      path = path.replace(/(?!.*\/).*/, schema.type);
    }

    if (
      schema?.type &&
      (force ||
        !this.renderer ||
        this.rendererKey !== `${schema.type}-${schema.$$id}`)
    ) {
      const rendererResolver = props.env.rendererResolver || resolveRenderer;
      this.renderer = rendererResolver(path, schema, props);
      this.rendererKey = `${schema.type}-${schema.$$id}`;
    } else {
      // 自定义组件如果在节点设置了 label name 什么的，就用 formItem 包一层
      // 至少自动支持了 valdiations, label, description 等逻辑。
      if (schema.children && !schema.component && schema.asFormItem) {
        schema.component = PlaceholderComponent;
        schema.renderChildren = schema.children;
        delete schema.children;
      }

      if (
        schema.component &&
        !schema.component.wrapedAsFormItem &&
        schema.asFormItem
      ) {
        const cache = componentCache.get(schema.component);
        if (cache) {
          schema.component = cache;
        } else {
          const cache = asFormItem({
            strictMode: false,
            ...schema.asFormItem
          })(schema.component);
          componentCache.set(schema.component, cache);
          cache.wrapedAsFormItem = true;
          schema.component = cache;
        }
      }
    }

    return { path, schema };
  }

  getWrappedInstance() {
    return this.ref;
  }

  refFn(ref: any) {
    this.ref = ref;
  }

  renderChild(
    region: string,
    node?: SchemaNode,
    subProps: {
      data?: object;
      [propName: string]: any;
    } = {}
  ) {
    let { schema: _, $path: __, env, ...rest } = this.props;
    let { path: $path } = this.resolveRenderer(this.props);
    const omitList = defaultOmitList.concat();
    // 手动调用检测 如果不通过也不用走下面了
    if (this.renderer) {
      const Component = this.renderer.component;
      Component.propsList &&
        omitList.push.apply(omitList, Component.propsList as Array<string>);
    }


    return renderChild(`${$path}${region ? `/${region}` : ''}`, node || '', {
      ...omit(rest, omitList),
      ...subProps,
      data: subProps.data || rest.data,
      env: env
    });
  }

  reRender() {
    this.resolveRenderer(this.props, true);
    this.forceUpdate();
  }

  render(): JSX.Element | null {
    let { $path: _, schema: __, ...rest } = this.props;
    if (__ == null) {
      return null;
    }

    let { path: $path, schema } = this.resolveRenderer(this.props);
    const theme = this.props.env.theme;

    if (Array.isArray(schema)) {
      return renderChildren($path, schema as any, rest) as JSX.Element;
    }

    const detectData =
      schema &&
      (schema.detectField === '&' ? rest : rest[schema.detectField || 'data']);
    const exprProps: any = detectData
      ? getExprProperties(schema, detectData, undefined, rest)
      : {};

    if (
      exprProps &&
      (exprProps?.hidden ||
        exprProps?.visible === false ||
        schema?.hidden ||
        schema?.visible === false ||
        rest?.hidden ||
        rest?.visible === false)
    ) {
      (rest as any).invisible = true;
    }

    if (schema.children) {
      return rest.invisible
        ? null
        : React.isValidElement(schema.children)
          ? schema.children
          : (schema.children as Function)({
            ...rest,
            ...exprProps,
            $path: $path,
            $schema: schema,
            render: this.renderChild,
            forwardedRef: this.refFn
          });
    } else if (typeof schema.component === 'function') {
      const isSFC = !(schema.component.prototype instanceof React.Component);
      const {
        data: defaultData,
        value: defaultValue,
        activeKey: defaultActiveKey,
        key: propKey,
        ...restSchema
      } = schema;
      return rest.invisible
        ? null
        : React.createElement(schema.component as any, {
          ...rest,
          ...restSchema,
          ...exprProps,
          defaultData,
          defaultValue,
          defaultActiveKey,
          propKey,
          $path: $path,
          $schema: schema,
          ref: isSFC ? undefined : this.refFn,
          forwardedRef: isSFC ? this.refFn : undefined,
          render: this.renderChild
        });
    } else if (Object.keys(schema).length === 0) {
      return null;
    } else if (!this.renderer) {
      return rest.invisible ? null : (
        <LazyComponent
          {...rest}
          {...exprProps}
          getComponent={async () => {
            const result = await rest.env.loadRenderer(
              schema,
              $path,
              this.reRender
            );
            if (result && typeof result === 'function') {
              return result;
            } else if (result && React.isValidElement(result)) {
              return () => result;
            }

            this.reRender();
            return () => loadRenderer(schema, $path);
          }}
          $path={$path}
          $schema={schema}
          retry={this.reRender}
        />
      );
    }

    const renderer = this.renderer as RendererConfig;
    schema = filterSchema(schema, renderer, rest);
    const {
      data: defaultData,
      value: defaultValue,
      key: propKey,
      activeKey: defaultActiveKey,
      ...restSchema
    } = schema;
    const Component = renderer.component;
    // 原来表单项的 visible: false 和 hidden: true 表单项的值和验证是有效的
    // 而 visibleOn 和 hiddenOn 是无效的，
    // 这个本来就是个bug，但是已经被广泛使用了
    // 我只能继续实现这个bug了
    if (
      rest.invisible &&
      (exprProps?.hidden ||
        exprProps?.visible === false ||
        !renderer.isFormItem ||
        (schema?.visible !== false && !schema?.hidden))
    ) {
      return null;
    }

    const component = (
      <BroadcastCmpt
        {...theme.getRendererConfig(renderer.name)}
        {...restSchema}
        {...chainEvents(rest, restSchema)}
        {...exprProps}
        defaultData={restSchema.defaultData ?? defaultData}
        defaultValue={restSchema.defaultValue ?? defaultValue}
        defaultActiveKey={defaultActiveKey}
        propKey={propKey}
        $path={$path}
        $schema={{ ...schema, ...exprProps }}
        ref={this.refFn}
        render={this.renderChild}
        component={Component}
      />
    );

    return component
  }
}

class PlaceholderComponent extends React.Component {
  render() {
    const { renderChildren, ...rest } = this.props as any;

    if (typeof renderChildren === 'function') {
      return renderChildren(rest);
    }

    return null;
  }
}
