import React from 'react';

import {Renderer, RendererProps} from '../factory';
import {SchemaNode, Schema, Action, PlainObject} from '../types';
import {CRUDStore, ICRUDStore} from '../store/crud';
import { LayoutModeEnum } from '../constants/table-enum'
import {
  createObject,
  extendObject,
  anyChanged,
  isObjectShallowModified,
  isVisible,
  getPropValue,
  getVariable,
  qsstringify,
  qsparse,
  isArrayChildrenModified
} from '../utils/helper';
import {ScopedContext} from '../Scoped';
import type {IScopedContext} from '../Scoped';
import Button from '../components/Button';
import Select from '../components/Select';
import getExprProperties from '../utils/filter-schema';
import pick from 'lodash/pick';
import {findDOMNode} from 'react-dom';
import {evalExpression, filter} from '../utils/tpl';
import {isEffectiveApi, isApiOutdated, str2function} from '../utils/api';
import omit from 'lodash/omit';
import find from 'lodash/find';
import findIndex from 'lodash/findIndex';
import Html from '../components/Html';
import {Spinner} from '../components';
import {Icon} from '../components/icons';
import {
  BaseSchema,
  SchemaApi,
  SchemaClassName,
  SchemaExpression,
  SchemaMessage,
  SchemaName,
  SchemaObject,
  SchemaTokenizeableString,
  SchemaTpl
} from '../Schema';
import {ActionSchema} from './Action';
import type {CardsSchema} from './Cards';
import type {ListSchema} from './List';
import {TableSchema} from './Table';
import {isPureVariable, resolveVariableAndFilter, resolveVariable} from '../utils/tpl-builtin';
import { CRUD_CONST } from '../constants/api'
import {AdvancedFilter, AdComponentIs, AdFilterSearchGroupBox} from './Table/AdvancedFilter'


/**
 * 将filter分组
 * @return {
 *  inside, 高级筛选里面的选择器
 *  outside 固定在外面的选择器
 * }
 */
function groupingFilter(filterList: any[], filter: any[]){
  let inside: any[] = [];
  let outside = [];
  let outSideOther: any = [];

  let list = filterList.slice()
  let outsideKey = filter.map(v=> v.key)

  outside = list.reduce((mo, v) => {
    if( ~outsideKey.indexOf(v.name) ){
      let {label, ...record} = v;

      let clearable: any = {}
      if(record.clearable !== void 0){
        // 将select组件的 clear icon 去掉 因为位置太紧凑了
        clearable.clearable = record.type === 'select'? false: record.clearable;
      }

      mo.push({
        ...record,
        required: false,// TODO. required 功能不好用 会提示必填文字 导致高度塌陷 后面将提示文字去掉 只将input框变红色
        placeholder: label,
        ...clearable
      })
    }else{
      outSideOther.push(v)
    }
    inside.push(v)
    return mo
  }, [])

  return {
    inside,
    outside,
    outSideOther
  }
}

export type CRUDBultinToolbarType =
  | 'columns-toggler'
  | 'drag-toggler'
  | 'pagination'
  | 'bulkActions'
  | 'bulk-actions'
  | 'statistics'
  | 'switch-per-page'
  | 'load-more'
  | 'filter-toggler'
  | 'export-csv'
  | 'export-excel';

export interface CRUDBultinToolbar extends Omit<BaseSchema, 'type'> {
  type: CRUDBultinToolbarType;
}

export type CRUDToolbarChild = SchemaObject | CRUDBultinToolbar;

export type CRUDToolbarObject = {
  /**
   * 对齐方式
   */
  align?: 'left' | 'right';
};

/**
 * TODO.
 * 后面需要优化这一块 表格的头按着三个模式分 需要注意的点
 * 1、功能和布局分开字段 不耦合，如 按钮参数就只放按钮schema 不放位置信息，headerToolbar就只放布局 这样可以自由组合上面三个模式
 * 2、高级筛选 表单类型和具体功能 应该是抽出来的 而不是在业务代码里面写 if else。这块参考设计模式 之策略模式
 */
export enum filterModeEnum {
  lite = 'lite',// 精简版
  advanced = 'advanced',// 高级版
  normal = 'normal',// 常规班
}


interface layoutItemType {
  label: string
  type: LayoutModeEnum
}

export type layoutListType = layoutItemType[]

export interface CRUDCommonSchema extends BaseSchema {
  /**
   *  指定为 CRUD 渲染器。
   */
  type: 'crud';

  /**
   * 指定内容区的展示模式。
   */
  mode?: 'table' | 'grid' | 'cards' | /* grid 的别名*/ 'list';

  /**
   * 初始化数据 API
   */
  api?: SchemaApi;

  /**
   * 初始化表格数据的前置 API
   */
  preApi?: SchemaApi;

  /**
   * filter 模式
   */
  filterMode: filterModeEnum;

  /**
   * 高级筛选api
   */
  adFilter?: {
    getApi?: SchemaApi;
    addApi?: SchemaApi;
    editApi?: SchemaApi;
    saveAllApi?: SchemaApi;
  }

  /**
   * 批量操作
   */
  bulkActions?: Array<ActionSchema>;

  /**
   * 单条操作
   */
  itemActions?: Array<ActionSchema>;

  /**
   * 每页个数，默认为 10，如果不是请设置。
   *
   * @default 10
   */
  perPage?: number;

  /**
   * 可以默认给定初始参数如： {\"perPage\": 24}
   */
  defaultParams?: PlainObject;

  /**
   * 是否可通过拖拽排序
   */
  draggable?: boolean;

  /**
   * 是否可通过拖拽排序，通过表达式来配置
   */
  draggableOn?: SchemaExpression;

  name?: SchemaName;

  /**
   * 初始是否拉取
   * @deprecated 建议用 api 的 sendOn 代替。
   */
  initFetch?: boolean;

  /**
   * 初始是否拉取，用表达式来配置。
   * @deprecated 建议用 api 的 sendOn 代替。
   */
  initFetchOn?: SchemaExpression;

  /**
   * 配置内部 DOM 的 className
   */
  innerClassName?: SchemaClassName;

  /**
   * 设置自动刷新时间
   */
  interval?: number;

  /**
   * 设置用来确定位置的字段名，设置后新的顺序将被赋值到该字段中。
   */
  orderField?: string;

  /**
   * 设置分页页码字段名。
   * @default page
   */
  pageField?: string;

  /**
   * 设置分页一页显示的多少条数据的字段名。
   * @default perPage
   */
  perPageField?: string;

  /**
   * 快速编辑后用来批量保存的 API
   */
  quickSaveApi?: SchemaApi;

  /**
   * 快速编辑配置成及时保存时使用的 API
   */
  quickSaveItemApi?: SchemaApi;

  /**
   * 保存排序的 api
   */
  saveOrderApi?: SchemaApi;

  /**
   * 是否将过滤条件的参数同步到地址栏,默认为true
   * @default true
   */
  syncLocation?: boolean;

  

  /**
   * 底部工具栏
   */
  footerToolbar?: Array<
    (CRUDToolbarChild & CRUDToolbarObject) | CRUDBultinToolbarType
  >;

  /**
   * 每页显示多少个空间成员的配置如： [10, 20, 50, 100]。
   */
  perPageAvailable?: Array<number>;

  messages?: SchemaMessage;

  /**
   * 是否隐藏快速编辑的按钮。
   */
  hideQuickSaveBtn?: boolean;

  /**
   * 是否自动跳顶部，当切分页的时候。
   */
  autoJumpToTopOnPagerChange?: boolean;

  /**
   * 静默拉取
   */
  silentPolling?: boolean;
  stopAutoRefreshWhen?: SchemaExpression;

  stopAutoRefreshWhenModalIsOpen?: boolean;
  /**
   * 筛选器开关是否显示 默认false
   */
  filterTogglable?: boolean;

  /**
   * 筛选器区域是否显示，只有当 filterTogglable为true的时候 才能设置成false，否则它会显示
   */
  filterDefaultVisible?: boolean;

  /**
   * 是否将接口返回的内容自动同步到地址栏，前提是开启了同步地址栏。
   */
  syncResponse2Query?: boolean;

  /**
   * 分页的时候是否保留用户选择。
   */
  keepItemSelectionOnPageChange?: boolean;

  /**
   * 当配置 keepItemSelectionOnPageChange 时有用，用来配置已勾选项的文案。
   */
  labelTpl?: SchemaTpl;

  /**
   * 是否为前端单次加载模式，可以用来实现前端分页。
   */
  loadDataOnce?: boolean;

  /**
   * 在开启loadDataOnce时，filter时是否去重新请求api
   */
  loadDataOnceFetchOnFilter?: boolean;

  /**
   * 也可以直接从环境变量中读取，但是不太推荐。
   */
  source?: SchemaTokenizeableString;

  /**
   * 如果时内嵌模式，可以通过这个来配置默认的展开选项。
   */
  expandConfig?: {
    /**
     * 默认是展开第一个、所有、还是都不展开。
     */
    expand?: 'first' | 'all' | 'none';

    /**
     * 是否显示全部切换按钮
     */
    expandAll?: boolean;

    /**
     * 是否为手风琴模式
     */
    accordion?: boolean;
  };

  /**
   * 默认只有当分页数大于 1 是才显示，如果总是想显示请配置。
   */
  alwaysShowPagination?: boolean;

  /**
   * 一屏显示 （内容区域占满屏幕剩余空间）
   */
  autoFillHeight?: boolean;

  /**
   * 所有的筛选条件
   */
  filterList?: any;

  /**
   * 表格头上的 行为按钮
   */
  headerAction?: any;
  
  /**
   * (废弃) 顶部工具栏
   */
   headerToolbar?: Array<
   (CRUDToolbarChild & CRUDToolbarObject) | CRUDBultinToolbarType
  >;

  /**
   * (废弃) 过滤器表单
   */
  filter?: any; // todo

  /**
   *  (废弃) 是否显示filter的边框和title 默认不显示
   */
  filterFrame?: boolean

  /**
   * (废弃) 开启查询区域，会根据列元素的searchable属性值，自动生成查询条件表单
   */
  autoGenerateFilter?: boolean;

  /**
   * 开启布局 默认开启 （前提是 filter 模式时 高级版，其他版本目前还不支持）
   */
  openLayout: boolean

  /**
   * 布局列表 默认只有表格布局
   */
  layoutList: layoutListType;

  /**
   * 选定的布局模式
   */
  layoutMode: LayoutModeEnum
}

export type CRUDCardsSchema = CRUDCommonSchema & {
  mode: 'cards';
} & Omit<CardsSchema, 'type'>;

export type CRUDListSchema = CRUDCommonSchema & {
  mode: 'list';
} & Omit<ListSchema, 'type'>;

export type CRUDTableSchema = CRUDCommonSchema & {
  mode?: 'table';
} & Omit<TableSchema, 'type'>;

/**
 * CRUD 增删改查渲染器。
 */
export type CRUDSchema = CRUDCardsSchema | CRUDListSchema | CRUDTableSchema;

export interface CRUDProps
  extends RendererProps,
    Omit<CRUDCommonSchema, 'type' | 'className'> {
  store: ICRUDStore;
  pickerMode?: boolean; // 选择模式，用做表单中的选择操作
}

export default class CRUD extends React.Component<CRUDProps, any> {
  static propsList: Array<keyof CRUDProps> = [
    'bulkActions',
    'itemActions',
    'mode',
    'orderField',
    'syncLocation',
    'toolbar',
    'toolbarInline',
    'messages',
    'value',
    'options',
    'multiple',
    'valueField',
    'defaultParams',
    'bodyClassName',
    'perPageAvailable',
    'pageField',
    'perPageField',
    'hideQuickSaveBtn',
    'autoJumpToTopOnPagerChange',
    'interval',
    'silentPolling',
    'stopAutoRefreshWhen',
    'stopAutoRefreshWhenModalIsOpen',
    'preApi',
    'api',
    'affixHeader',
    'columnsTogglable',
    'placeholder',
    'tableClassName',
    'headerClassName',
    'footerClassName',
    // 'toolbarClassName',
    'footerToolbar',
    'filterTogglable',
    'filterDefaultVisible',
    'autoGenerateFilter',
    'syncResponse2Query',
    'keepItemSelectionOnPageChange',
    'labelTpl',
    'labelField',
    'loadDataOnce',
    'loadDataOnceFetchOnFilter',
    'source',
    'header',
    'columns',
    'size',
    'onChange',
    'onInit',
    'onSaved',
    'onQuery',
    'formStore',
    'autoFillHeight',
    'filterList',
    'headerAction',
    'adFilter',
    'headerToolbar',
    // 废弃
    'filterFrame',
  ];
  static defaultProps = {
    toolbarInline: true,
    headerToolbar: ['bulkActions'],
    footerToolbar: [
      {
        "align": "left",
        "type": "statistics"
      },
      {
        "align": "right",
        "type": "pagination"
      },
      {
        "align": "right",
        "type": "switch-per-page"
      }
    ],
    primaryField: 'id',
    syncLocation: true,
    pageField: CRUD_CONST.pageField,
    perPageField: CRUD_CONST.perPageField,
    hideQuickSaveBtn: false,
    autoJumpToTopOnPagerChange: true,
    silentPolling: false,
    filterTogglable: false,
    filterDefaultVisible: true,
    loadDataOnce: false,
    loadDataOnceFetchOnFilter: true,
    filterFrame: false,
    autoFillHeight: true,
    filterMode: filterModeEnum.lite,
    filterList: [],
    openLayout: true,
    layoutList: [
      {type: LayoutModeEnum.table, label: '表格'},
      {type: LayoutModeEnum.wide, label: '宽详情'},
      {type: LayoutModeEnum.narrow, label: '窄详情'},
    ],
    layoutMode: LayoutModeEnum.table
  };

  control: any;
  lastQuery: any;
  timer: ReturnType<typeof setTimeout>;
  mounted: boolean;
  filterRef: any;
  tableRef: any;
  constructor(props: CRUDProps) {
    super(props);

    this.tableRef = React.createRef()

    this.controlRef = this.controlRef.bind(this);
    this.handleFilterReset = this.handleFilterReset.bind(this);
    this.handleFilterSubmit = this.handleFilterSubmit.bind(this);
    this.handleFilterInit = this.handleFilterInit.bind(this);
    this.handleAction = this.handleAction.bind(this);
    this.handleBulkAction = this.handleBulkAction.bind(this);
    this.handleChangePage = this.handleChangePage.bind(this);
    this.handleBulkGo = this.handleBulkGo.bind(this);
    this.handleDialogConfirm = this.handleDialogConfirm.bind(this);
    this.handleDialogClose = this.handleDialogClose.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleSaveOrder = this.handleSaveOrder.bind(this);
    this.handleSelect = this.handleSelect.bind(this);
    this.handleChildPopOverOpen = this.handleChildPopOverOpen.bind(this);
    this.handleChildPopOverClose = this.handleChildPopOverClose.bind(this);
    this.search = this.search.bind(this);
    this.silentSearch = this.silentSearch.bind(this);
    this.handleQuery = this.handleQuery.bind(this);
    this.renderHeaderToolbar = this.renderHeaderToolbar.bind(this);
    this.renderFooterToolbar = this.renderFooterToolbar.bind(this);
    this.renderAdFilter = this.renderAdFilter.bind(this);
    this.clearSelection = this.clearSelection.bind(this);
    this.renderHeaderAction = this.renderHeaderAction.bind(this);
    this.getAction = this.getAction.bind(this);

    const {
      location,
      store,
      pageField,
      perPageField,
      syncLocation,
      filterTogglable,
      filterDefaultVisible,
      openLayout,
      layoutMode,
      layoutList,
    } = props;

    let lMode = layoutMode
    if(openLayout){
      if(!layoutList.filter(v=> v.type === layoutMode).length){
        lMode = layoutList?.[0]?.type || LayoutModeEnum.table
      }

      // 如果开启了多布局模式 默认显示模式用传进来的，（提前是传进来的这个 是在layoutList里面存在的）
      store.setLayoutMode(lMode)
    }

    this.mounted = true;

    if (syncLocation && location && (location.query || location.search)) {
      store.updateQuery(
        qsparse(location.search.substring(1)),
        undefined,
        pageField,
        perPageField
      );
    } else if (syncLocation && !location && window.location.search) {
      store.updateQuery(
        qsparse(window.location.search.substring(1)) as object,
        undefined,
        pageField,
        perPageField
      );
    }

    store.setFilterTogglable(!!filterTogglable, filterDefaultVisible);

    // 如果当前crud是需要通过api 接口获取数据的，调接口之前先清空，防止拿到的是上层数据
    if (this.props.api) {
      store.updateData({
        items: []
      });
    }
  }

  componentDidMount() {
    const {store, adFilter, filterMode} = this.props;

    if (this.props.perPage) {
      store.changePage(store.page, this.props.perPage);
    }

    // 如果是高级筛选模式 需要先查询卡片列表
    if(filterMode === filterModeEnum.advanced && adFilter?.getApi && isEffectiveApi(adFilter.getApi)){
      this.handleInitPreData(adFilter.getApi);
    }

    if(filterMode === filterModeEnum.lite){
      this.handleFilterInit({});
    }

    if(filterMode === filterModeEnum.normal){
      if (
        (!this.props.filter || (store.filterTogggable && !store.filterVisible))
      ) {
        this.handleFilterInit({});
      }
    }

    let val: any;
    if (this.props.pickerMode && (val = getPropValue(this.props))) {
      store.setSelectedItems(val);
    }
  }

  componentDidUpdate(prevProps: CRUDProps) {
    const props = this.props;
    const store = prevProps.store;

    if (
      anyChanged(
        ['toolbar', 'headerToolbar', 'footerToolbar', 'bulkActions'],
        prevProps,
        props
      )
    ) {
      // 来点参数变化。
      this.renderHeaderToolbar = this.renderHeaderToolbar.bind(this);
      this.renderFooterToolbar = this.renderFooterToolbar.bind(this);
    }

    let val: any;
    if (
      this.props.pickerMode &&
      isArrayChildrenModified(
        (val = getPropValue(this.props)),
        getPropValue(prevProps)
      )
    ) {
      store.setSelectedItems(val);
    }

    if (this.props.filterTogglable !== prevProps.filterTogglable) {
      store.setFilterTogglable(
        !!props.filterTogglable,
        props.filterDefaultVisible
      );
    }

    let dataInvalid = false;

    if (
      prevProps.syncLocation &&
      prevProps.location &&
      prevProps.location.search !== props.location.search
    ) {
      // 同步地址栏，那么直接检测 query 是否变了，变了就重新拉数据
      store.updateQuery(
        qsparse(props.location.search.substring(1)),
        undefined,
        props.pageField,
        props.perPageField
      );
      dataInvalid = !!(
        props.api && isObjectShallowModified(store.query, this.lastQuery, false)
      );
    }

    if (dataInvalid) {
      // 要同步数据
    } else if (
      prevProps.api &&
      props.api &&
      isApiOutdated(
        prevProps.api,
        props.api,
        store.fetchCtxOf(prevProps.data, {
          pageField: prevProps.pageField,
          perPageField: prevProps.perPageField
        }),
        store.fetchCtxOf(props.data, {
          pageField: props.pageField,
          perPageField: props.perPageField
        })
      )
    ) {
      dataInvalid = true;
    } else if (!props.api && isPureVariable(props.source)) {
      const prev = resolveVariableAndFilter(
        prevProps.source,
        prevProps.data,
        '| raw'
      );
      const next = resolveVariableAndFilter(props.source, props.data, '| raw');

      if (prev !== next) {
        store.initFromScope(props.data, props.source);
      }
    }

    if (dataInvalid) {
      this.search();
    }
  }

  componentWillUnmount() {
    this.mounted = false;
    clearTimeout(this.timer);
  }

  controlRef(control: any) {
    // 因为 control 有可能被 n 层 hoc 包裹。
    while (control && control.getWrappedInstance) {
      control = control.getWrappedInstance();
    }

    this.control = control;
  }

  handleAction(
    e: React.UIEvent<any> | undefined,
    action: Action,
    ctx: object,
    throwErrors: boolean = false,
    delegate?: IScopedContext
  ): any {
    const {
      onAction,
      store,
      messages,
      pickerMode,
      env,
      pageField,
      stopAutoRefreshWhenModalIsOpen
    } = this.props;

    if (action.actionType === 'dialog') {
      store.setCurrentAction(action);
      const idx: number = (ctx as any).index;
      const length = store.items.length;
      stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
      store.openDialog(ctx, {
        hasNext: idx < length - 1,
        nextIndex: idx + 1,
        hasPrev: idx > 0,
        prevIndex: idx - 1,
        index: idx
      });
    } else if (action.actionType === 'ajax') {
      store.setCurrentAction(action);
      const data = ctx;

      // 由于 ajax 一段时间后再弹出，肯定被浏览器给阻止掉的，所以提前弹。
      const redirect = action.redirect && filter(action.redirect, data);
      redirect && action.blank && env.jumpTo(redirect, action);

      let actionApi = action.api!;
      if(action.realType === 'download' && typeof actionApi === 'object'){// 导出带上查询参数
        actionApi = {
          // @ts-ignore
          ...actionApi, 
          data: {
            ...store.getAdFilter(),
            ...store.getDynamicField(),
            ...store.query
          }
        }
      }

      return store
        .saveRemote(actionApi, data, {
          successMessage:
            (action.messages && action.messages.success) ||
            (messages && messages.saveSuccess),
          errorMessage:
            (action.messages && action.messages.failed) ||
            (messages && messages.saveFailed)
        })
        .then(async (payload: object) => {
          const data: any = createObject(ctx, payload);

          if (action.feedback && isVisible(action.feedback, data)) {
            await this.openFeedback(action.feedback, data);
            stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
          }

          if(action.realType === 'download'){
            if((top as any)?.global?.export && typeof (top as any).global.export === 'function'){
              (top as any).global.export({
                id: data.TASK,
              })
            }
          }else{
            const redirect = action.redirect && filter(action.redirect, data);
            redirect && !action.blank && env.jumpTo(redirect, action);
            action.reload
              ? this.reloadTarget(action.reload, data)
              : redirect
              ? null
              : this.search(undefined, undefined, true, true);
            action.close && this.closeTarget(action.close);
          }
        })
        .catch(() => {});
    } else if (
      pickerMode &&
      (action.actionType === 'confirm' || action.actionType === 'submit')
    ) {
      store.setCurrentAction(action);
      return Promise.resolve({
        items: store.selectedItems.concat()
      });
    } else if (action.onClick) {
      store.setCurrentAction(action);
      let onClick = action.onClick;
      if (typeof onClick === 'string') {
        onClick = str2function(onClick, 'event', 'props', 'data');
      }
      onClick && onClick(e, this.props, ctx);
    } else {
      onAction(e, action, ctx, throwErrors, delegate || this.context);
    }
  }

  handleBulkAction(
    selectedItems: Array<any>,
    unSelectedItems: Array<any>,
    e: React.UIEvent<any>,
    action: Action
  ) {
    const {
      store,
      primaryField,
      onAction,
      messages,
      pageField,
      stopAutoRefreshWhenModalIsOpen,
      env
    } = this.props;

    if (!selectedItems.length && action.requireSelected !== false) {
      return;
    }

    let ids = selectedItems
      .map(item =>
        item.hasOwnProperty(primaryField) ? item[primaryField as string] : null
      )
      .filter(item => item)
      .join(',');

    const ctx = createObject(store.mergedData, {
      ...selectedItems[0],
      rows: selectedItems,
      items: selectedItems,
      unSelectedItems: unSelectedItems,
      ids
    });

    let fn = () => {
      if (action.actionType === 'dialog') {
        return this.handleAction(
          e,
          {
            ...action,
            __from: 'bulkAction'
          },
          ctx
        );
      } else if (action.actionType === 'ajax') {
        isEffectiveApi(action.api, ctx) &&
          store
            .saveRemote(action.api as string, ctx, {
              successMessage:
                (action.messages && action.messages.success) ||
                (messages && messages.saveSuccess),
              errorMessage:
                (action.messages && action.messages.failed) ||
                (messages && messages.saveFailed)
            })
            .then(async (payload: object) => {
              const data = createObject(ctx, payload);
              if (action.feedback && isVisible(action.feedback, data)) {
                await this.openFeedback(action.feedback, data);
                stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
              }

              action.reload
                ? this.reloadTarget(action.reload, data)
                : this.search(
                    {[pageField || CRUD_CONST.pageField]: 1},
                    undefined,
                    true,
                    true
                  );
              action.close && this.closeTarget(action.close);

              const redirect = action.redirect && filter(action.redirect, data);
              redirect && env.jumpTo(redirect, action);
            })
            .catch(() => null);
      } else if (onAction) {
        onAction(e, action, ctx, false, this.context);
      }
    };

    if (action.confirmText && env.confirm) {
      env
        .confirm(filter(action.confirmText, ctx))
        .then((confirmed: boolean) => confirmed && fn());
    } else {
      fn();
    }
  }

  handleItemAction(action: Action, ctx: any) {
    this.doAction(action, ctx);
  }

  handleInitPreData(preApi: string){
    const {store, messages} = this.props;

    store
      .fetchPreData(preApi, {}, {
        successMessage: messages && messages.fetchSuccess,
        errorMessage: messages && messages.fetchFailed,
        silent: true,// 这个就不转圈了
      })
      .then(this.handleFilterInit.bind(this, {}));

  }

  handleFilterInit(values: object) {
    const {defaultParams, store} = this.props;

    this.handleFilterSubmit(
      {
        ...defaultParams,
        ...values,
        ...store.query
      },
      false,
      true,
      this.props.initFetch !== false
    );

    store.setPristineQuery();

    const {pickerMode, options} = this.props;

    pickerMode &&
      store.updateData({
        items: options || []
      });
  }

  handleFilterReset(values: object) {
    const {store, syncLocation, env, pageField, perPageField} = this.props;

    store.updateQuery(
      store.pristineQuery,
      syncLocation && env && env.updateLocation
        ? (location: any) => env.updateLocation(location)
        : undefined,
      pageField,
      perPageField,
      true
    );
    this.lastQuery = store.query;
    this.search();
  }

  handleFilterSubmit(
    values: object,
    jumpToFirstPage: boolean = true,
    replaceLocation: boolean = false,
    search: boolean = true
  ) {
    console.time('开始点击')
    const {
      store,
      syncLocation,
      env,
      pageField,
      perPageField,
      loadDataOnceFetchOnFilter
    } = this.props;

    values = syncLocation
      ? qsparse(qsstringify(values, undefined, true))
      : values;

    // TODO 这块操作 会让 crud组件和 table组件强制更新一次 （这是个无效更新 耗时大概 82ms）
    store.updateQuery(
      {
        ...values,
        [pageField || CRUD_CONST.pageField]: jumpToFirstPage ? 1 : store.page
      },
      syncLocation && env && env.updateLocation
        ? (location: any) => env.updateLocation(location, replaceLocation)
        : undefined,
      pageField,
      perPageField
    );

    this.lastQuery = store.query;
    search &&
      this.search(undefined, undefined, undefined, loadDataOnceFetchOnFilter);
  }

  handleBulkGo(
    selectedItems: Array<any>,
    unSelectedItems: Array<any>,
    e: React.MouseEvent<any>
  ) {
    const action = this.props.store.selectedAction;
    const env = this.props.env;

    if (action.confirmText) {
      return env
        .confirm(action.confirmText)
        .then(
          (confirmed: boolean) =>
            confirmed &&
            this.handleBulkAction(
              selectedItems,
              unSelectedItems,
              e as any,
              action
            )
        );
    }

    return this.handleBulkAction(
      selectedItems,
      unSelectedItems,
      e as any,
      action
    );
  }

  handleDialogConfirm(
    values: object[],
    action: Action,
    ctx: any,
    components: Array<any>
  ) {
    const {
      store,
      pageField,
      stopAutoRefreshWhenModalIsOpen,
      interval,
      silentPolling,
      env
    } = this.props;

    store.closeDialog(true);
    const dialogAction = store.action as Action;

    if (stopAutoRefreshWhenModalIsOpen && interval) {
      this.timer = setTimeout(
        silentPolling ? this.silentSearch : this.search,
        Math.max(interval, 1000)
      );
    }

    if (
      action.actionType === 'next' &&
      typeof ctx.nextIndex === 'number' &&
      store.data.items[ctx.nextIndex]
    ) {
      return this.handleAction(
        undefined,
        {
          ...dialogAction
        },
        createObject(
          createObject(store.data, {
            index: ctx.nextIndex
          }),
          store.data.items[ctx.nextIndex]
        )
      );
    } else if (
      action.actionType === 'prev' &&
      typeof ctx.prevIndex === 'number' &&
      store.data.items[ctx.prevIndex]
    ) {
      return this.handleAction(
        undefined,
        {
          ...dialogAction
        },
        createObject(
          createObject(store.data, {
            index: ctx.prevIndex
          }),
          store.data.items[ctx.prevIndex]
        )
      );
    } else if (values.length) {
      const value = values[0];
      ctx = createObject(ctx, value);
      const component = components[0];

      // 提交来自 form
      if (component && component.props.type === 'form') {
        // 数据保存了，说明列表数据已经无效了，重新刷新。
        if (value && (value as any).__saved) {
          const reload = action.reload ?? dialogAction.reload;
          // 配置了 reload 则跳过自动更新。
          reload ||
            this.search(
              dialogAction.__from ? {[pageField || CRUD_CONST.pageField]: 1} : undefined,
              undefined,
              true,
              true
            );
        } else if (
          value &&
          ((value.hasOwnProperty('items') && (value as any).items) ||
            value.hasOwnProperty('ids')) &&
          this.control.bulkUpdate
        ) {
          this.control.bulkUpdate(value, (value as any).items);
        }
      }
    }

    const reload = action.reload ?? dialogAction.reload;
    if (reload) {
      this.reloadTarget(reload, ctx);
    }

    let redirect = action.redirect ?? dialogAction.redirect;
    redirect = redirect && filter(redirect, ctx);
    redirect && env.jumpTo(redirect, dialogAction);
  }

  handleDialogClose(confirmed = false) {
    const {store, stopAutoRefreshWhenModalIsOpen, silentPolling, interval} =
      this.props;
    store.closeDialog(confirmed);

    if (stopAutoRefreshWhenModalIsOpen && interval) {
      this.timer = setTimeout(
        silentPolling ? this.silentSearch : this.search,
        Math.max(interval, 1000)
      );
    }
  }

  openFeedback(dialog: any, ctx: any) {
    return new Promise(resolve => {
      const {store} = this.props;
      store.setCurrentAction({
        type: 'button',
        actionType: 'dialog',
        dialog: dialog
      });
      store.openDialog(ctx, undefined, confirmed => {
        resolve(confirmed);
      });
    });
  }

  search(
    values?: any,
    silent?: boolean,
    clearSelection?: boolean,
    forceReload = false
  ) {
    const {
      store,
      api,
      messages,
      pageField,
      perPageField,
      interval,
      stopAutoRefreshWhen,
      stopAutoRefreshWhenModalIsOpen,
      silentPolling,
      syncLocation,
      syncResponse2Query,
      keepItemSelectionOnPageChange,
      pickerMode,
      env,
      loadDataOnce,
      loadDataOnceFetchOnFilter,
      source
    } = this.props;

    // reload 需要清空用户选择。
    if (keepItemSelectionOnPageChange && clearSelection && !pickerMode) {
      store.setSelectedItems([]);
      store.setUnSelectedItems([]);
    }

    let loadDataMode = '';
    if (values && typeof values.loadDataMode === 'string') {
      loadDataMode = 'load-more';
      delete values.loadDataMode;
    }

    clearTimeout(this.timer);
    values &&
      store.updateQuery(
        values,
        !loadDataMode && syncLocation && env && env.updateLocation
          ? env.updateLocation
          : undefined,
        pageField,
        perPageField
      );
    this.lastQuery = store.query;
    const data = createObject(store.data, store.query);
    let adFilter = store.getAdFilter();
    let dynamicField = store.getDynamicField();
    
    console.timeEnd('开始点击')
    console.time('结束点击')

    isEffectiveApi(api, data)
      ? store
          .fetchInitData(api, {...data, ...adFilter, ...dynamicField}, {
            successMessage: messages && messages.fetchSuccess,
            errorMessage: messages && messages.fetchFailed,
            autoAppend: true,
            forceReload,
            loadDataOnce,
            loadDataOnceFetchOnFilter,
            source,
            silent,
            pageField,
            perPageField,
            loadDataMode,
            syncResponse2Query
          })
          .then(value => {
            console.timeEnd('结束点击')
            interval &&
              this.mounted &&
              (!stopAutoRefreshWhen ||
                !(
                  (stopAutoRefreshWhenModalIsOpen && store.hasModalOpened) ||
                  evalExpression(
                    stopAutoRefreshWhen,
                    createObject(store.data, store.query)
                  )
                )) &&
              (this.timer = setTimeout(
                silentPolling
                  ? this.silentSearch.bind(this, undefined, undefined, true)
                  : this.search.bind(
                      this,
                      undefined,
                      undefined,
                      undefined,
                      true
                    ),
                Math.max(interval, 1000)
              ));
            return value;
          })
      : source && store.initFromScope(data, source);
  }

  silentSearch(values?: object, clearSelection?: boolean, forceReload = false) {
    return this.search(values, true, clearSelection, forceReload);
  }

  handleChangePage(page: number, perPage?: number) {
    const {
      store,
      syncLocation,
      env,
      pageField,
      perPageField,
      autoJumpToTopOnPagerChange,
      affixOffsetTop
    } = this.props;

    let query: any = {
      [pageField || CRUD_CONST.pageField]: page
    };

    if (perPage) {
      query[perPageField || CRUD_CONST.perPageField] = perPage;
    }

    store.updateQuery(
      query,
      syncLocation && env?.updateLocation ? env.updateLocation : undefined,
      pageField,
      perPageField
    );

    this.search(undefined, undefined, undefined);

    if (autoJumpToTopOnPagerChange && this.control) {
      (findDOMNode(this.control) as HTMLElement).scrollIntoView();
      const scrolledY = window.scrollY;
      const offsetTop = affixOffsetTop ?? env?.affixOffsetTop ?? 0;
      scrolledY && window.scroll(0, scrolledY - offsetTop);
    }
  }

  handleSave(
    rows: Array<object> | object,
    diff: Array<object> | object,
    indexes: Array<string>,
    unModifiedItems?: Array<any>,
    rowsOrigin?: Array<object> | object,
    resetOnFailed?: boolean
  ) {
    const {
      store,
      quickSaveApi,
      quickSaveItemApi,
      primaryField,
      env,
      messages,
      reload
    } = this.props;

    if (Array.isArray(rows)) {
      if (!isEffectiveApi(quickSaveApi)) {
        env && env.alert('CRUD quickSaveApi is required');
        return;
      }

      const data: any = createObject(store.data, {
        rows,
        rowsDiff: diff,
        indexes: indexes,
        rowsOrigin
      });

      if (rows.length && rows[0].hasOwnProperty(primaryField || 'id')) {
        data.ids = rows
          .map(item => (item as any)[primaryField || 'id'])
          .join(',');
      }

      if (unModifiedItems) {
        data.unModifiedItems = unModifiedItems;
      }

      store
        .saveRemote(quickSaveApi, data, {
          successMessage: messages && messages.saveFailed,
          errorMessage: messages && messages.saveSuccess
        })
        .then(() => {
          reload && this.reloadTarget(reload, data);
          this.search(undefined, undefined, true, true);
        })
        .catch(() => {});
    } else {
      if (!isEffectiveApi(quickSaveItemApi)) {
        env && env.alert('CRUD quickSaveItemApi is required!');
        return;
      }

      const data = createObject(store.data, {
        item: rows,
        modified: diff,
        origin: rowsOrigin
      });

      const sendData = createObject(data, rows);
      store
        .saveRemote(quickSaveItemApi, sendData)
        .then(() => {
          reload && this.reloadTarget(reload, data);
          this.search(undefined, undefined, true, true);
        })
        .catch(() => {
          resetOnFailed && this.control.reset();
        });
    }
  }

  handleSaveOrder(moved: Array<object>, rows: Array<object>) {
    const {store, saveOrderApi, orderField, primaryField, env, reload} =
      this.props;

    if (!saveOrderApi) {
      env && env.alert('CRUD saveOrderApi is required!');
      return;
    }

    const model: {
      insertAfter?: any;
      insertBefore?: any;
      idMap?: any;
      rows?: any;
      ids?: any;
      order?: any;
    } = createObject(store.data);

    let insertAfter: any;
    let insertBefore: any;
    const holding: Array<object> = [];
    const hasIdField =
      primaryField &&
      rows[0] &&
      (rows[0] as object).hasOwnProperty(primaryField);

    hasIdField || (model.idMap = {});

    model.insertAfter = {};
    rows.forEach((item: any) => {
      if (~moved.indexOf(item)) {
        if (insertAfter) {
          let insertAfterId = hasIdField
            ? (insertAfter as any)[primaryField as string]
            : rows.indexOf(insertAfter);
          model.insertAfter[insertAfterId] =
            (model as any).insertAfter[insertAfterId] || [];

          hasIdField || (model.idMap[insertAfterId] = insertAfter);
          model.insertAfter[insertAfterId].push(
            hasIdField ? item[primaryField as string] : item
          );
        } else {
          holding.push(item);
        }
      } else {
        insertAfter = item;
        insertBefore = insertBefore || item;
      }
    });

    if (insertBefore && holding.length) {
      let insertBeforeId = hasIdField
        ? insertBefore[primaryField as string]
        : rows.indexOf(insertBefore);
      hasIdField || (model.idMap[insertBeforeId] = insertBefore);
      model.insertBefore = {};
      model.insertBefore[insertBeforeId] = holding.map((item: any) =>
        hasIdField ? item[primaryField as string] : item
      );
    } else if (holding.length) {
      const first: any = holding[0];
      const firstId = hasIdField
        ? first[primaryField as string]
        : rows.indexOf(first);

      hasIdField || (model.idMap[firstId] = first);
      model.insertAfter[firstId] = holding
        .slice(1)
        .map((item: any) => (hasIdField ? item[primaryField as string] : item));
    }

    if (orderField) {
      const start = (store.page - 1) * store.perPage || 0;
      rows = rows.map((item, key) =>
        extendObject(item, {
          [orderField]: start + key + 1
        })
      );
    }

    model.rows = rows.concat();
    hasIdField &&
      (model.ids = rows
        .map((item: any) => item[primaryField as string])
        .join(','));
    hasIdField &&
      orderField &&
      (model.order = rows.map(item =>
        pick(item, [primaryField as string, orderField])
      ));

    isEffectiveApi(saveOrderApi, model) &&
      store
        .saveRemote(saveOrderApi, model)
        .then(() => {
          reload && this.reloadTarget(reload, model);
          this.search(undefined, undefined, true, true);
        })
        .catch(() => {});
  }

  handleSelect(items: Array<any>, unSelectedItems: Array<any>) {
    const {
      store,
      keepItemSelectionOnPageChange,
      primaryField,
      multiple,
      pickerMode,
      onSelect
    } = this.props;

    let newItems = items;
    let newUnSelectedItems = unSelectedItems;

    if (keepItemSelectionOnPageChange && store.selectedItems.length) {
      const oldItems = store.selectedItems.concat();
      const oldUnselectedItems = store.unSelectedItems.concat();

      items.forEach(item => {
        const idx = findIndex(
          oldItems,
          a =>
            a === item ||
            (a[primaryField || 'id'] &&
              a[primaryField || 'id'] == item[primaryField || 'id'])
        );

        if (~idx) {
          oldItems[idx] = item;
        } else {
          oldItems.push(item);
        }

        const idx2 = findIndex(
          oldUnselectedItems,
          a =>
            a === item ||
            (a[primaryField || 'id'] &&
              a[primaryField || 'id'] == item[primaryField || 'id'])
        );

        if (~idx2) {
          oldUnselectedItems.splice(idx2, 1);
        }
      });

      unSelectedItems.forEach(item => {
        const idx = findIndex(
          oldUnselectedItems,
          a =>
            a === item ||
            (a[primaryField || 'id'] &&
              a[primaryField || 'id'] == item[primaryField || 'id'])
        );

        const idx2 = findIndex(
          oldItems,
          a =>
            a === item ||
            (a[primaryField || 'id'] &&
              a[primaryField || 'id'] == item[primaryField || 'id'])
        );

        if (~idx) {
          oldUnselectedItems[idx] = item;
        } else {
          oldUnselectedItems.push(item);
        }

        ~idx2 && oldItems.splice(idx2, 1);
      });

      newItems = oldItems;
      newUnSelectedItems = oldUnselectedItems;

      // const thisBatch = items.concat(unSelectedItems);
      // let notInThisBatch = (item: any) =>
      //   !find(
      //     thisBatch,
      //     a => a[primaryField || 'id'] == item[primaryField || 'id']
      //   );

      // newItems = store.selectedItems.filter(notInThisBatch);
      // newUnSelectedItems = store.unSelectedItems.filter(notInThisBatch);

      // newItems.push(...items);
      // newUnSelectedItems.push(...unSelectedItems);
    }

    if (pickerMode && multiple === false && newItems.length > 1) {
      newUnSelectedItems.push.apply(
        newUnSelectedItems,
        newItems.splice(0, newItems.length - 1)
      );
    }

    store.setSelectedItems(newItems);
    store.setUnSelectedItems(newUnSelectedItems);
    onSelect && onSelect(newItems);
  }

  handleChildPopOverOpen(popOver: any) {
    if (
      this.props.interval &&
      popOver &&
      ~['dialog', 'drawer'].indexOf(popOver.mode)
    ) {
      this.props.stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
      this.props.store.setInnerModalOpened(true);
    }
  }

  handleChildPopOverClose(popOver: any) {
    const {stopAutoRefreshWhenModalIsOpen, silentPolling, interval} =
      this.props;

    if (popOver && ~['dialog', 'drawer'].indexOf(popOver.mode)) {
      this.props.store.setInnerModalOpened(false);

      if (stopAutoRefreshWhenModalIsOpen && interval) {
        this.timer = setTimeout(
          silentPolling ? this.silentSearch : this.search,
          Math.max(interval, 1000)
        );
      }
    }
  }

  handleQuery(values: object, forceReload: boolean = false) {
    const {store, syncLocation, env, pageField, perPageField} = this.props;

    store.updateQuery(
      {
        ...values,
        [pageField || CRUD_CONST.pageField]: 1
      },
      syncLocation && env && env.updateLocation
        ? env.updateLocation
        : undefined,
      pageField,
      perPageField
    );
    this.search(undefined, undefined, undefined, forceReload);
  }

  reload(subpath?: string, query?: any) {
    if (query) {
      return this.receive(query);
    } else {
      this.search(undefined, undefined, true, true);
    }
  }

  receive(values: object) {
    this.handleQuery(values, true);
  }

  reloadTarget(target: string, data: any) {
    // implement this.
  }

  closeTarget(target: string) {
    // implement this.
  }

  doAction(action: Action, data: object, throwErrors: boolean = false) {
    return this.handleAction(undefined, action, data, throwErrors);
  }

  unSelectItem(item: any, index: number) {
    const {store} = this.props;
    const selected = store.selectedItems.concat();
    const unSelected = store.unSelectedItems.concat();

    const idx = selected.indexOf(item);
    ~idx && unSelected.push.apply(unSelected, selected.splice(idx, 1));

    store.setSelectedItems(selected);
    store.setUnSelectedItems(unSelected);
  }

  clearSelection() {
    const {store} = this.props;
    const selected = store.selectedItems.concat();
    const unSelected = store.unSelectedItems.concat();

    store.setSelectedItems([]);
    store.setUnSelectedItems(unSelected.concat(selected));
  }

  hasBulkActionsToolbar() {
    const {headerToolbar, footerToolbar} = this.props;

    const isBulkActions = (item: any) =>
      ~['bulkActions', 'bulk-actions'].indexOf(item.type || item);
    return (
      (Array.isArray(headerToolbar) && find(headerToolbar, isBulkActions)) ||
      (Array.isArray(footerToolbar) && find(footerToolbar, isBulkActions))
    );
  }

  hasBulkActions() {
    const {bulkActions, itemActions, store} = this.props;

    if (!bulkActions || !bulkActions.length) {
      return false;
    }

    let bulkBtns: Array<ActionSchema> = [];
    const ctx = store.mergedData;

    if (bulkActions && bulkActions.length) {
      bulkBtns = bulkActions
        .map(item => ({
          ...item,
          ...getExprProperties(item as Schema, ctx)
        }))
        .filter(item => !item.hidden && item.visible !== false);
    }

    return bulkBtns.length;
  }

  renderBulkActions(childProps: any) {
    let {bulkActions, itemActions, store, render, classnames: cx} = this.props;

    if (!bulkActions || !bulkActions.length) {
      return null;
    }

    const selectedItems = store.selectedItems;
    const unSelectedItems = store.unSelectedItems;

    let bulkBtns: Array<ActionSchema> = [];
    let itemBtns: Array<ActionSchema> = [];

    const ctx = store.mergedData;

    // const ctx = createObject(store.data, {
    //     ...store.query,
    //     items: childProps.items,
    //     selectedItems: childProps.selectedItems,
    //     unSelectedItems: childProps.unSelectedItems
    // });

    if (
      bulkActions &&
      bulkActions.length &&
      (!itemActions || !itemActions.length || selectedItems.length > 1)
    ) {
      bulkBtns = bulkActions
        .map(item => ({
          ...item,
          ...getExprProperties(item as Schema, ctx)
        }))
        .filter(item => !item.hidden && item.visible !== false);
    }

    const itemData = createObject(
      store.data,
      selectedItems.length ? selectedItems[0] : {}
    );

    if (itemActions && selectedItems.length <= 1) {
      itemBtns = itemActions
        .map(item => ({
          ...item,
          ...getExprProperties(item as Schema, itemData)
        }))
        .filter(item => !item.hidden && item.visible !== false);
    }

    return bulkBtns.length || itemBtns.length ? (
      <div className={cx('Crud-actions')}>
        {bulkBtns.map((btn, index) =>
          render(
            `bulk-action/${index}`,
            {
              ...omit(btn, ['visibleOn', 'hiddenOn', 'disabledOn']),
              type: 'button',
              ignoreConfirm: true
            },
            {
              key: `bulk-${index}`,
              data: ctx,
              disabled:
                btn.disabled ||
                (btn.requireSelected !== false ? !selectedItems.length : false),
              onAction: this.handleBulkAction.bind(
                this,
                selectedItems.concat(),
                unSelectedItems.concat()
              )
            }
          )
        )}

        {itemBtns.map((btn, index) =>
          render(
            `bulk-action/${index}`,
            {
              ...omit(btn, ['visibleOn', 'hiddenOn', 'disabledOn']),
              type: 'button'
            },
            {
              key: `item-${index}`,
              data: itemData,
              disabled: btn.disabled || selectedItems.length !== 1,
              onAction: this.handleItemAction.bind(this, btn, itemData)
            }
          )
        )}
      </div>
    ) : null;
  }

  renderPagination(toolbar: SchemaNode) {
    const {store, render, classnames: cx, alwaysShowPagination} = this.props;

    const {page, lastPage} = store;

    if (
      store.mode !== 'simple' &&
      store.lastPage < 2 &&
      !alwaysShowPagination
    ) {
      return null;
    }

    const extraProps: any = {};
    if (typeof toolbar !== 'string') {
      extraProps.showPageInput = (toolbar as Schema).showPageInput;
      extraProps.maxButtons = (toolbar as Schema).maxButtons;
    }

    return (
      <div className={cx('Crud-pager')}>
        {render(
          'pagination',
          {
            type: 'pagination'
          },
          {
            ...extraProps,
            activePage: page,
            lastPage: lastPage,
            hasNext: store.hasNext,
            mode: store.mode,
            onPageChange: this.handleChangePage
          }
        )}
      </div>
    );
  }

  renderStatistics() {
    const {
      store,
      classnames: cx,
      translate: __,
      alwaysShowPagination,
    } = this.props;

    if (store.lastPage <= 1 && !alwaysShowPagination) {
      // return null;
    }

    return (
      <div className={cx('Crud-statistics')}>
        {__(store.layoutMode === LayoutModeEnum.wide? 'CRUD.statcut':'CRUD.stat', {
          page: store.page,
          lastPage: store.lastPage,
          total: store.total,
          curPageSize: store.curPageSize
        })}
      </div>
    );
  }

  renderSwitchPerPage(childProps: any) {
    const {
      store,
      perPageAvailable,
      classnames: cx,
      classPrefix: ns,
      translate: __
    } = this.props;

    const items = childProps.items;

    if (!items.length) {
      return null;
    }

    const perPages = (perPageAvailable || [5, 10, 20, 50, 100]).map(
      (item: any) => ({
        label: item,
        value: item + ''
      })
    );

    return (
      <div className={cx('Crud-pageSwitch')}>
        {__('CRUD.perPage')}
        <Select
          classPrefix={ns}
          searchable={false}
          placeholder={__('Select.placeholder')}
          options={perPages}
          value={store.perPage + ''}
          onChange={(value: any) => this.handleChangePage(1, value.value)}
          clearable={false}
        />
      </div>
    );
  }

  renderLoadMore() {
    const {store, classPrefix: ns, classnames: cx, translate: __} = this.props;
    const {page, lastPage} = store;

    return page < lastPage ? (
      <div className={cx('Crud-loadMore')}>
        <Button
          classPrefix={ns}
          onClick={() =>
            this.search({page: page + 1, loadDataMode: 'load-more'})
          }
          size="sm"
        >
          {__('CRUD.loadMore')}
        </Button>
      </div>
    ) : (
      ''
    );
  }

  renderFilterToggler() {
    const {store, classnames: cx, translate: __} = this.props;

    if (!store.filterTogggable) {
      return null;
    }

    return (
      <button
        onClick={() => store.setFilterVisible(!store.filterVisible)}
        className={cx('Button Button--sm Button--default', {
          'is-active': store.filterVisible
        })}
      >
        <Icon icon="filter" className="icon m-r-xs" />
        {__('CRUD.filter')}
      </button>
    );
  }

  renderExportCSV(toolbar: Schema) {
    const {
      store,
      classPrefix: ns,
      classnames: cx,
      translate: __,
      loadDataOnce,
      data
    } = this.props;

    const api = (toolbar as Schema).api;

    return (
      <Button
        classPrefix={ns}
        onClick={() =>
          store.exportAsCSV({
            loadDataOnce,
            api,
            data
          })
        }
        size="sm"
      >
        {toolbar.label || __('CRUD.exportCSV')}
      </Button>
    );
  }

  renderToolbar(
    toolbar?: SchemaNode,
    index: number = 0,
    childProps: any = {},
    toolbarRenderer?: (toolbar: SchemaNode, index: number) => React.ReactNode
  ) {
    if (!toolbar) {
      return null;
    }

    const {render, store, translate: __} = this.props;
    const type = (toolbar as Schema).type || toolbar;

    if (type === 'bulkActions' || type === 'bulk-actions') {
      return this.renderBulkActions(childProps);
    } else if (type === 'pagination') {
      return this.renderPagination(toolbar);
    } else if (type === 'statistics') {
      return this.renderStatistics();
    } else if (type === 'switch-per-page') {
      return this.renderSwitchPerPage(childProps);
    } else if (type === 'load-more') {
      return this.renderLoadMore();
    } else if (type === 'filter-toggler') {
      return this.renderFilterToggler();
    } else if (type === 'export-csv') {
      return this.renderExportCSV(toolbar as Schema);
    } else if (type === 'reload') {
      let reloadButton = {
        label: '',
        icon: 'fa fa-sync',
        tooltip: __('reload'),
        tooltipPlacement: 'top',
        type: 'button'
      };
      if (typeof toolbar === 'object') {
        reloadButton = {...reloadButton, ...omit(toolbar, ['type', 'align'])};
      }
      return render(`toolbar/${index}`, reloadButton, {
        onAction: () => {
          this.reload();
        }
      });
    } else if (Array.isArray(toolbar)) {
      const children: Array<any> = toolbar
        .filter((toolbar: any) => isVisible(toolbar, store.filterData))
        .map((toolbar, index) => ({
          dom: this.renderToolbar(toolbar, index, childProps, toolbarRenderer),
          toolbar
        }))
        .filter(item => item.dom);
      const len = children.length;
      const cx = this.props.classnames;
      if (len) {
        return (
          <div className={cx('Crud-toolbar')} key={index}>
            {children.map(({toolbar, dom: child}, index) => {
              const type = (toolbar as Schema).type || toolbar;
              let align =
                toolbar.align || (type === 'pagination' ? 'right' : 'left');
              return (
                <div
                  key={index}
                  className={cx(
                    'Crud-toolbar-item',
                    align ? `Crud-toolbar-item--${align}` : '',
                    toolbar.className
                  )}
                >
                  {child}
                </div>
              );
            })}
          </div>
        );
      }
      return null;
    }

    const result = toolbarRenderer
      ? toolbarRenderer(toolbar, index)
      : undefined;

    if (result !== void 0) {
      return result;
    }

    const $$editable = childProps.$$editable;

    return render(`toolbar/${index}`, toolbar, {
      // 包两层，主要是为了处理以下 case
      // 里面放了个 form，form 提交过来的时候不希望把 items 这些发送过来。
      // 因为会把数据呈现在地址栏上。
      data: createObject(
        createObject(store.filterData, {
          items: childProps.items,
          selectedItems: childProps.selectedItems,
          unSelectedItems: childProps.unSelectedItems
        }),
        {}
      ),
      page: store.page,
      lastPage: store.lastPage,
      perPage: store.perPage,
      total: store.total,
      onQuery: this.handleQuery,
      onAction: this.handleAction,
      onChangePage: this.handleChangePage,
      onBulkAction: this.handleBulkAction,
      $$editable
    });
  }
  getAdFilterList(advancedFilter: any[], curAdFilterId: string | null){
    let _advancedFilter = advancedFilter.slice()

      let label = '快速筛选器';
      let unSaveList: any = [];
      let privateList: any = [];
      let commonList: any = [];
      _advancedFilter.forEach((v: any) => {
        if(v.CARD_ID === curAdFilterId){
          label = v.CARD_NAME
        }
        let obj = {
          id: v.CARD_ID,
          type: 'button',
          label: v.CARD_NAME
        }
        if(v.CARD_TYPE === 'C'){
          commonList.push(obj)
        }else{
          if(v.isSave){
            privateList.push(obj)
          }else{
            unSaveList.push(obj)
          }
        }
      })

      let list = []

      list.push({
        "type": "button",
        "label": "无",
        id: null,
      })

      if(commonList.length){
        if(privateList.length || unSaveList.length){
          commonList.push({
            "type": "divider"
          })
        }
        list.push({
          "label": "公共",
          "icon": "fa fa-share-alt",
          "children": [
            ...commonList,
          ]
        })
      }
      if(privateList.length){
        list.push({
          "label": "私有",
          "icon": "fa fa-user",
          "children": privateList
        })
      }

      if(unSaveList.length){
        list.push({
          "label": "未保存",
          "icon": "fa fa-user",
          "children": unSaveList
        })
      }
      
      return {
        label,
        list,
      }
  }
  renderHeaderAction(
    childProps: any,
    toolbarRenderer?: (toolbar: SchemaNode) => React.ReactNode
  ){
    let { headerAction, filterList, classnames: cx,
      render, store, translate: __, filterMode, openLayout, layoutList } = this.props;

    let { 
      action,// 左上角按钮
      actionMore,// 更多操作下面的按钮
      filter,// 显示在外面的过滤器 
    } = headerAction || {};
    filter = Array.isArray(filter)? filter: [];

    let btns: SchemaNode  = [];
    let itemLeft = []
    let itemRight: any = [];

    if(Array.isArray(action) && action.length){
      btns = btns.concat(action)
    }
    if(Array.isArray(actionMore) && actionMore.length){
      btns.push({
        "type": "dropdown-button",
        "label": __('CRUD.moreAction'),
        "buttons": actionMore
      })
    }

    const $$editable = childProps.$$editable;

    itemLeft = btns.map((actionItem: any, index)=>{
      let child = null
      if(typeof actionItem === 'string'){
        child = this.renderToolbar(actionItem)

      }else{
        child = render(`headerAction/${index}`, actionItem, {
          data: createObject(
            createObject(createObject((store.filterData as any).__super), {
              items: childProps.items,
              selectedItems: childProps.selectedItems,
              unSelectedItems: childProps.unSelectedItems
            }),
            {}
          ),
          page: store.page,
          lastPage: store.lastPage,
          perPage: store.perPage,
          total: store.total,
          onQuery: this.handleQuery,
          onAction: this.handleAction,
          onChangePage: this.handleChangePage,
          onBulkAction: this.handleBulkAction,
          $$editable
        }); 
      }

      return (
        <div
          key={index}
          className={cx(
            'Crud-header-action-item',
            actionItem.className
          )}
        >
          {child}
        </div>
      );
    })

    itemRight = []

    // 常驻filter
    let _filter = groupingFilter(filterList, filter)
    if(_filter.outside.length){
      let filterDom = render(
        'filter',
        {
          mode: 'inline',
          type: 'form',
          api: null,
          wrapWithPanel: false,
          title: '',
          className: 'form-input-7x',
          submitText: '',
          controls: _filter.outside,
        },
        {
          key: 'filter',
          panelClassName: cx(
            'Crud-filter',
            'Panel--default'
          ),
          data: store.filterData,
          onRef: (content: any) => {
            this.filterRef = content
          },
          onReset: this.handleFilterReset,
          onSubmit: this.handleFilterSubmit,
          formStore: undefined
        }
      )
      itemRight.push(filterDom)
    }
    
    // 动态组件
    if(_filter.outSideOther.length){
      itemRight.push(<AdComponentIs
        key='component-is' 
        classnames={cx}
        filter={_filter.outSideOther}
        dynamicFieldKey={store.dynamicFieldKey}
        dynamicFieldValue={store.dynamicFieldValue}
        onSelectFieldValue={store.setDynamicFieldValue}
        onSelectFieldKey={store.setDynamicFieldKey}
        resolveVariable={(source) => resolveVariable(source, this.props.data)}
        />
      )
    }

    // 高级筛选下拉
    let adFilterData = this.getAdFilterList(store.advancedFilter, store.curAdFilterId)
    

    if(filterMode !== filterModeEnum.lite){
      let adFilterList = render(
        'dropdown',
        {
          "type": "dropdown-button",
          "label": adFilterData.label,
          "className": 'Crud-header-dropdown',
          "closeOnClick": true,
          "buttons": adFilterData.list,
          "contentWidth": 66,
        },
        {
          key: 'dropdown-layout-mode',
          onAction: (_: any, action: any) => {
            store.setCurAdFilterId(action.id)
  
            _filter.outside.length? this.filterRef.handleAction(undefined, {actionType: 'submit'}): this.handleFilterSubmit({})
          }
        }
      )

      itemRight.push(adFilterList)
    }

    // 查询重置按钮
    let control_btn = null
    control_btn = (
      <AdFilterSearchGroupBox 
      key={'header-control-btn'}
      classnames={cx}
      toggable={store.adFilterToggable}
      onSearch={() => {
        _filter.outside.length? this.filterRef.handleAction(undefined, {actionType: 'submit'}): this.handleFilterSubmit({})
      }}
      showToggable={filterMode !== filterModeEnum.lite}
      showResetBtn={_filter.outside.length || _filter.outSideOther.length}
      onReset={() => {
        store.setDynamicFieldValue(null)

        if(_filter.outside.length){
          this.filterRef.handleAction(undefined, {actionType: 'reset'})
        }else{
          this.handleFilterSubmit({})
        }
      }}
      onToggable={()=> {
        setTimeout(() => {
          this.tableRef.current?.updateAutoFillHeight?.()
        }, 300)
        store.setAdFilterToggable(!store.adFilterToggable)
      }}
      />
    )
    itemRight.push(control_btn)


    if(filterMode === filterModeEnum.advanced && openLayout){
      let tableLayout = render(
        'dropdown',
        {
          "type": "dropdown-button",
          "label": store.layoutModeText,
          "className": 'Crud-header-dropdown',
          "closeOnClick": true,
          "buttons": layoutList.map((v: layoutItemType)=>{
            return {
              "type": "button",
              "label": v.label,
              "id": v.type
            }
          }),
          "contentWidth": 40,
        },
        {
          key: 'dropdown',
          onAction: (_: any, action: any) => {
            store.setLayoutMode(action.id)
          }
        }
      )

      itemRight.push(tableLayout)
    }

    // 列控制器
    const result = toolbarRenderer? toolbarRenderer({
      "type": "columns-toggler",
      "draggable": true
    },): null;
    itemRight.push(result)

    return (
      <div className={cx('Crud-header-action')}>
        <div className={cx('Crud-header-action--left')}>
          {itemLeft}
        </div>
        <div className={cx('Crud-header-action--right')}>
          {itemRight}
        </div>
      </div>
    );

  }

  renderHeaderToolbar(
    childProps: any,
    toolbarRenderer?: (toolbar: SchemaNode, index: number) => React.ReactNode
  ) {
    let {toolbar, toolbarInline, headerToolbar} = this.props;

    if (toolbar) {
      if (Array.isArray(headerToolbar)) {
        headerToolbar = toolbarInline
          ? headerToolbar.concat(toolbar)
          : [headerToolbar, toolbar];
      } else if (headerToolbar) {
        headerToolbar = [headerToolbar, toolbar];
      } else {
        headerToolbar = toolbar;
      }
    }

    return this.renderToolbar(
      headerToolbar || [],
      0,
      childProps,
      toolbarRenderer
    );
  }

  renderFooterToolbar(
    childProps: any,
    toolbarRenderer?: (toolbar: SchemaNode, index: number) => React.ReactNode
  ) {
    let {toolbar, toolbarInline, footerToolbar} = this.props;

    if (toolbar) {
      if (Array.isArray(footerToolbar)) {
        footerToolbar = toolbarInline
          ? footerToolbar.concat(toolbar)
          : [footerToolbar, toolbar];
      } else if (footerToolbar) {
        footerToolbar = [footerToolbar, toolbar];
      } else {
        footerToolbar = toolbar;
      }
    }

    return this.renderToolbar(footerToolbar, 0, childProps, toolbarRenderer);
  }

  renderSelection(): React.ReactNode {
    const {
      store,
      classnames: cx,
      labelField,
      labelTpl,
      primaryField,
      translate: __
    } = this.props;

    if (!store.selectedItems.length) {
      return null;
    }

    return (
      <div className={cx('Crud-selection')}>
        <div className={cx('Crud-selectionLabel')}>{__('CRUD.selected')}</div>
        {store.selectedItems.map((item, index) => (
          <div key={index} className={cx(`Crud-value`)}>
            <span
              data-tooltip={__('delete')}
              data-position="bottom"
              className={cx('Crud-valueIcon')}
              onClick={this.unSelectItem.bind(this, item, index)}
            >
              ×
            </span>
            <span className={cx('Crud-valueLabel')}>
              {labelTpl ? (
                <Html html={filter(labelTpl, item)} />
              ) : (
                getVariable(item, labelField || 'label') ||
                getVariable(item, primaryField || 'id')
              )}
            </span>
          </div>
        ))}
        <a onClick={this.clearSelection} className={cx('Crud-selectionClear')}>
          {__('clear')}
        </a>
      </div>
    );
  }

  renderAdFilter(){
    const {
      filterList,
      headerAction,
      adFilter,
      classnames,
      store,
      translate,
      data,
      env
    } = this.props;

    let _filter = Array.isArray(headerAction?.filter)? headerAction.filter: [];
    let insideFilter = groupingFilter(filterList, _filter).inside
    
    return (
      <AdvancedFilter 
      classnames={classnames}
      translate={translate}
      env={env}
      filter={insideFilter} 
      resolveVariable={(source: any) => resolveVariable(source, this.props.data)}
      adFilter={adFilter}

      adFilterToggable={store.adFilterToggable}

      advancedFilter={store.advancedFilter}
      advancedFilterOriginal={store.advancedFilterOriginal}
  
      curCardId={store.curCardId}
      curAdFilterId={store.curAdFilterId}
      curAdvancedFilterCard={store.curAdvancedFilterCard}
  
      setCurCardId={(id: any) => {
        store.setCurCardId(id);
        setTimeout(() => {
          this.tableRef.current?.updateAutoFillHeight?.()
        }, 100)
      }}
      setCurAdFilterId={store.setCurAdFilterId}
      setAdvancedFilter={(list: any) => {
        store.setAdvancedFilter(list)
        setTimeout(() => {
          this.tableRef.current?.updateAutoFillHeight?.()
        }, 100)
      }}
      setAdvancedFilterOriginal={store.setAdvancedFilterOriginal}

      adFilterRequest={store.adFilterRequest}
      />
    )
  }

  getAction(){
    let { headerAction } = this.props;
    return headerAction.action
  }
 
  render() {
    const {
      className,
      bodyClassName,
      filter,
      render,
      store,
      mode,
      syncLocation,
      children,
      bulkActions,
      pickerMode,
      multiple,
      valueField,
      primaryField,
      value,
      hideQuickSaveBtn,
      itemActions,
      classnames: cx,
      keepItemSelectionOnPageChange,
      maxKeepItemSelectionLength,
      onAction,
      popOverContainer,
      translate: __,
      onQuery,
      filterFrame,
      autoGenerateFilter,
      onSelect,
      autoFillHeight,
      filterList,
      headerAction,
      filterMode,
      openLayout,
      layoutList,
      rowInfo,
      ...rest
    } = this.props;


    return (
      <div
        className={cx('Crud', className, {
          'is-loading': store.loading
        })}
      >

        {filterMode === filterModeEnum.normal && filter && (!store.filterTogggable || store.filterVisible)
          ? render(
              'filter',
              {
                title: filterFrame? __('CRUD.filter'): '',
                mode: 'inline',
                submitText: __('search'),
                ...filter,
                type: 'form',
                api: null,
                wrapWithPanel: filterFrame,
              },
              {
                key: 'filter',
                panelClassName: cx(
                  'Crud-filter',
                  filter.panelClassName || 'Panel--default'
                ),
                data: store.filterData,
                onReset: this.handleFilterReset,
                onSubmit: this.handleFilterSubmit,
                onInit: this.handleFilterInit,
                formStore: undefined
              }
            )
          : null}

        {keepItemSelectionOnPageChange && multiple !== false
          ? this.renderSelection()
          : null}

        {render(
          'body',
          {
            ...rest,
            columns: store.columns ?? rest.columns,
            type: mode || 'table'
          },
          {
            key: 'body',
            className: cx('Crud-body', bodyClassName),
            ref: this.controlRef,
            autoGenerateFilter: !filter && autoGenerateFilter,
            autoFillHeight: autoFillHeight,
            selectable: !!(
              (this.hasBulkActionsToolbar() && this.hasBulkActions()) ||
              pickerMode
            ),
            itemActions,
            multiple:
              multiple === void 0
                ? bulkActions && bulkActions.length > 0
                  ? true
                  : false
                : multiple,
            selected:
              pickerMode || keepItemSelectionOnPageChange
                ? store.selectedItemsAsArray
                : undefined,
            keepItemSelectionOnPageChange,
            maxKeepItemSelectionLength,
            valueField: valueField || primaryField,
            primaryField: primaryField,
            hideQuickSaveBtn,
            filterList,
            items: store.data.items,
            query: store.query,
            orderBy: store.query.orderBy,
            orderDir: store.query.orderDir,
            popOverContainer,
            onAction: this.handleAction,
            onSave: this.handleSave,
            onSaveOrder: this.handleSaveOrder,
            onQuery: this.handleQuery,
            onSelect: this.handleSelect,
            onPopOverOpened: this.handleChildPopOverOpen,
            onPopOverClosed: this.handleChildPopOverClose,
            onSearchableFromReset: this.handleFilterReset,
            onSearchableFromSubmit: this.handleFilterSubmit,
            onSearchableFromInit: this.handleFilterInit,
            headerToolbarRender: this.renderHeaderToolbar,
            footerToolbarRender: this.renderFooterToolbar,
            headerActionRender: this.renderHeaderAction,
            adFilterRender: this.renderAdFilter,
            getAction: this.getAction,
            filterMode: filterMode,
            data: store.mergedData,
            tableRef: (self: any) => {
              this.tableRef.current = self
            },
            layoutMode: store.layoutMode,
            openLayout: openLayout,
            layoutList: layoutList,
            rowInfo: rowInfo,
          }
        )}

        <Spinner overlay size="lg" key="info" show={store.loading} />

        {render(
          'dialog',
          {
            ...((store.action as Action) &&
              ((store.action as Action).dialog as object)),
            type: 'dialog'
          },
          {
            key: 'dialog',
            data: store.dialogData,
            onConfirm: this.handleDialogConfirm,
            onClose: this.handleDialogClose,
            show: store.dialogOpen
          }
        )}
      </div>
    );
  }
}

@Renderer({
  type: 'crud',
  storeType: CRUDStore.name,
  isolateScope: true
})
export class CRUDRenderer extends CRUD {
  static contextType = ScopedContext;

  constructor(props: CRUDProps, context: IScopedContext) {
    super(props);

    const scoped = context;
    scoped.registerComponent(this);
  }

  componentWillUnmount() {
    super.componentWillUnmount();
    const scoped = this.context as IScopedContext;
    scoped.unRegisterComponent(this);
  }

  reload(subpath?: string, query?: any, ctx?: any) {
    const scoped = this.context as IScopedContext;
    if (subpath) {
      return scoped.reload(
        query ? `${subpath}?${qsstringify(query)}` : subpath,
        ctx
      );
    }

    return super.reload(subpath, query);
  }

  receive(values: any, subPath?: string) {
    const scoped = this.context as IScopedContext;
    if (subPath) {
      return scoped.send(subPath, values);
    }

    return super.receive(values);
  }

  reloadTarget(target: string, data: any) {
    const scoped = this.context as IScopedContext;
    scoped.reload(target, data);
  }

  closeTarget(target: string) {
    const scoped = this.context as IScopedContext;
    scoped.close(target);
  }
}
