/* eslint-disable no-new-func */

import { isEmptyObj, getScopeSlotMeta, isSlot } from '../common';

import {
  IPageComponent,
  IPlatformListener,
  IBasicComponent,
  IComplexComponent,
  deserializeAttributes,
  deserializeValue,
  PropBindType,
  ActionType,
  IDataBind,
  deserializeDynamicMap,
  EComponentType,
  IPlatformComponent,
  processCommonStyle2CSSProperties,
  processCSSPropertiesUnit,
  IPlatformDataset,
  OFFICIAL_LIB_KEY,
} from '@cloudbase/cals';
import { uniq as _uniq, map as _map, get as lodashGet } from 'lodash-es';
import { runtime } from './index';
import { REPEATER } from '../constants';
import { CALC_RPX_ROOT_VAL_NAME } from '../index';
import { IAppAPI, ICompositeComponentAPI, IPageAPI } from 'src/app/global-api';
import { IQueryContext, Query } from '../query';
import { EventFlow, IEventFlowContext } from '../flow';
import { componentMetaFilter } from './dependencies';

/**
 * utils start
 */

interface ISyncProp {
  changeEvent: string;
  valueFromEvent: string;
}

interface IComponentControlMeta {
  name: string;
  title: string;
  platform: string[];
  shortcut: any;
}

export interface IDataFrom {
  type: string;
  syncProp?: ISyncProp;
  /**
   * @deprecated
   */
  inputProp?: ISyncProp;
  ['x-slot-scope']?: { [key: string]: any };
  'x-slot-directives'?: any;
}

export interface IComponentControl {
  name: string;
  /**
   * 入参表单配置
   */
  dataForm: {
    [prop: string]: IDataFrom;
  };

  /**
   * 只读属性配置
   * JSON schema
   */
  properties: { type: 'object'; properties: { [prop: string]: { type: string; title: string } } };
  /**
   * 事件配置
   */
  emitEvents: {
    /**
     * 事件标识
     */
    eventName: string;
    /**
     * 事件名
     */
    name: string;
  }[];
  events?: { name: string }[];

  meta?: IComponentControlMeta;
  /**
   * 组件静态配置
   */
  compConfig?: any;
  inputProps: any[];
  syncProps?: any[];
  isContainer: boolean;
}

export function getComponentsInfo(dependencies): { [sourceKey: string]: IComponentInfo } {
  const outputObj = {};
  dependencies.forEach(({ name: materialName, components, isComposite }) => {
    components.forEach(
      ({
        name,
        meta,
        emitEvents,
        events = undefined,
        isContainer,
        compConfig,
        dataForm,
        properties,
      }: IComponentControl) => {
        outputObj[`${materialName}:${name}`] = {
          isComposite,
          ...componentMetaFilter(meta),
          events,
          emitEvents,
          isContainer,
          compConfig,
          dataForm,
          properties,
        };
      },
    );
  });
  return outputObj;
}

function getInputProps(componentsMetaMap: Record<string, IComponentInfo>) {
  const outputObj = {};
  // eslint-disable-next-line no-restricted-syntax
  for (let key in componentsMetaMap) {
    let component = componentsMetaMap[key];
    const sourceKey = key;

    const inputProps = component?.inputProps || component?.syncProps;
    if (inputProps) {
      outputObj[sourceKey] = inputProps;
    }

    let compItem = component;
    Object.keys(compItem.dataForm || {}).forEach((key) => {
      const inputProps = compItem.dataForm[key]?.inputProp || compItem.dataForm[key]?.syncProp;
      if (inputProps) {
        outputObj[sourceKey] = {
          [key]: inputProps,
          ...(outputObj[sourceKey] || {}),
        };
      }
    });
  }
  return outputObj;
}

/**
 * utils end
 */

export function splitDynamicMap(data): { staticData: any; dataBinds: IDataBind[] } {
  const staticData = {};
  const dataBinds = [];
  for (let key in data) {
    let meta = data[key];
    if (!meta.type || meta.type === PropBindType.static) {
      staticData[key] = meta.value;
    } else if (meta.type === PropBindType.slot) {
      continue;
    } else {
      dataBinds.push({
        type: meta.type,
        bindDataPath: meta.value,
        propertyPath: key,
      });
    }
  }
  return { staticData, dataBinds };
}

export interface IOriginKeyInfo {
  sourceKey: string;
  name: string;
  materialName: string;
  materialVersion: string;
  key: string;
  variableName: string;
  type?: ActionType;
  isComposite?: boolean;
  isPlainProps?: boolean;
  entry?: string;
}

export interface IGenerateContext {
  $app: IAppAPI;
  $page?: IPageAPI;
  $comp?: ICompositeComponentAPI;
  /**
   * @deprecated
   */
  this?: any; // 复合组件注入bind this 使用
  actionsMap?: Record<string, Function>;
}

export interface IGenerateOptions {
  isComposite?: boolean;
  looseError?: boolean;
  key?: string;
  /**
   * 使用 widget 数据直接计算
   */
  useScopeWidgetData?: boolean;
}

function walkThroughCals<P>(
  cals,
  process: (
    item: P & {
      extra: Required<IPlatformComponent>['extra'] & {
        __parent: any;
      };
    },
    options,
  ) => void,
  options = {},
) {
  let { items = [] } = cals;
  cals.extra = cals.extra || {};
  if (cals?.directives?.[':if'] === false) {
    cals.items = [];
  }
  const filteredItems = [];
  for (let item of items) {
    if (item?.directives?.[':if'] === false) {
      continue;
    }
    filteredItems.push(item);
    item.extra = item.extra || {};
    item.extra.__parent = cals;
  }
  cals.items = filteredItems;
  // 先根遍历 因为父上的状态可能需要带下去
  process(cals, options);
  for (let item of cals.items) {
    walkThroughCals(item, process, options);
  }
  return cals;
}

function parseDirectiveIfAndDispaly(directiveValue) {
  let value;
  switch (directiveValue) {
    case true:
    case false: {
      value = directiveValue;
      break;
    }
    case undefined: {
      value = true;
      break;
    }
  }
  return value;
}

export interface IComponentInfo extends Omit<IComponentControl, 'meta' | 'type' | 'name'>, IComponentControlMeta {
  isComposite: boolean;
}

/**
 * widgets 不包括page 的 widges
 */
export function getComponentSchema(
  ctx: IGenerateContext,
  page: IPageComponent | { items: IPlatformComponent[]; type: typeof EComponentType.COMPLEX; extra: any },
  componentsInfoMap: { [componentSourceKey: string]: IComponentInfo } = {},
  wrapperClass?: string,
  options?: {
    isComposite?: boolean;
    looseError?: boolean;
    processCssUnit?: 'rem' | 'px' | 'rpx';
  },
) {
  const { processCssUnit = 'rem', looseError = false, isComposite = false } = options || {};
  const componentInputProps = getInputProps(componentsInfoMap) || {};
  const copyJson = JSON.parse(
    JSON.stringify(page, (key, value) => {
      /**
       * 处理默认的 module 补全
       */
      if (value?.component && !value?.module && value?.type !== 'PAGE') {
        value.module = OFFICIAL_LIB_KEY;
      }
      if (value?.component) {
        const component = value;
        if (
          value?.component &&
          (`${component.module}:${component.component}` === `${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_NAME}` ||
            `${component.module}:${component.component}` === `${REPEATER.DEBUG_MODULE_NAME}:${REPEATER.REPEATER_NAME}`)
        ) {
          // 给 Repeater 组件加一层虚拟项组件，在虚拟项组件上挂 for 循环
          const { items = [] } = component;
          const firstChildComponent = items[0];
          if (
            `${firstChildComponent?.module}:${firstChildComponent?.component}` ===
              `${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_ITEM_NAME}` ||
            `${firstChildComponent?.module}:${firstChildComponent?.component}` ===
              `${REPEATER.DEBUG_MODULE_NAME}:${REPEATER.REPEATER_ITEM_NAME}`
          ) {
            return value;
          }
          component.items = [
            {
              component: REPEATER.REPEATER_ITEM_NAME,
              module: component.module,
              directives: {
                ':if': true,
                ':for': component.attributes[':data'],
                key: component.attributes.key || '',
                ':display': component.directives?.[':display'],
              },
              items: items.map((item) => {
                let currentDisplay = component.directives?.[':display'];
                if (currentDisplay !== undefined && currentDisplay !== 'true') {
                  if (!item.directives) {
                    item.directives = {};
                  }
                  const current = item.directives[':display'] || 'true';
                  item.directives[':display'] = `(\n${currentDisplay}\n) && (\n${current}\n)`;
                }
                return item;
              }),
              id: `$weda_internal_${component.id}`, // uniqueId() 解决在编辑器中切换组件导致 children 不断增加的问题
            },
          ];
        }
      }
      return value;
    }),
  );
  const compWidgets = {};
  const compDataBinds = {};

  const componentSchemaJson = walkThroughCals<IBasicComponent | IComplexComponent>(
    copyJson,
    (component, options = {}) => {
      const parentComponent = component.extra.__parent;

      if (isSlot(component)) {
        if (parentComponent) {
          const slotId = `${parentComponent.id}.${component.id}`;
          const parentComponentSourceKey = `${parentComponent.module}:${parentComponent.component}`;
          const sourceKey = `${parentComponentSourceKey}.${component.id}`;
          const {
            isScopeSlot,
            config: scopeSlotConfig,
            useScopeWidgetData = false,
          } = getScopeSlotMeta(parentComponent, component.id, {
            componentsInfoMap,
          });
          if (isScopeSlot) {
            compWidgets[slotId] = {
              _visible: true,
              _display: true,
              style: {},
              classList: [],
              widgetType: sourceKey,
              _parentId: parentComponent.id,
              getConfig: () => ({}),
              _waForKey: undefined,
            };

            if (!useScopeWidgetData) {
              options.useScopeWidgetData = false;
            }

            if (scopeSlotConfig?.['x-slot-directives']?.for) {
              compDataBinds[slotId] = generateDataBinds(
                ctx,
                [
                  {
                    type: PropBindType.expression,
                    bindDataPath: `$w.${parentComponent.id}.${scopeSlotConfig?.['x-slot-directives']?.for}`,
                    // bindDataPath: '$page.dataset.state.list',
                    propertyPath: '_waFor',
                  },
                ],
                {
                  isComposite,
                  looseError,
                  key: `组件 ${slotId}`,
                  useScopeWidgetData: true,
                },
              );
            }
          }
        }
        return;
      }
      const { component: componentName, module, attributes = {} } = component;
      const { commonStyle = {} } = component.extra || {};
      const rpxCalcRoot = `var(${CALC_RPX_ROOT_VAL_NAME})`;
      const processedCommonStyle = attributes.style
        ? processCSSPropertiesUnit(attributes.style || {}, processCssUnit, { rpxCalcRoot })
        : processCommonStyle2CSSProperties(commonStyle, {
            defaultUnit: processCssUnit,
            rpxCalcRoot,
          });
      if (processedCommonStyle) {
        component.extra.commonStyle = processedCommonStyle;
      }
      const sourceKey = `${module}:${componentName}`;
      // 如果是复合组件的根节点，则补充 wrapperClass
      if (isComposite) {
        if (component?.extra?.__parent && !component?.extra?.__parent?.extra?.__parent) {
          if (!component.attributes) {
            component.attributes = {};
          }
          component.attributes.class = Array.from(new Set([component.attributes.class, wrapperClass?.split(/\s/)]))
            .filter((item) => !!item)
            .join(' ');
        }
      }

      const { class: className = '' } = attributes;

      /**
       * TODO:
       * 此处有变更，之前强行声明 page 为传进来的 schema
       */
      const deserializeCtx = isComposite ? { page: page as any } : {};

      let dynamicStyle = component[':style']
        ? deserializeValue(deserializeCtx, ':style', component[':style']).value
        : undefined;
      let dynamicClassList = component[':class']
        ? deserializeValue(deserializeCtx, ':class', component[':class']).value
        : undefined;

      let dynamicIf = component.directives?.[':if']
        ? deserializeValue(deserializeCtx, ':if', component.directives?.[':if']).value
        : undefined;

      let dynamicDisplay = component.directives?.[':display']
        ? deserializeValue(deserializeCtx, ':display', component.directives?.[':display']).value
        : undefined;

      let dynamicFor = component.directives?.[':for']
        ? deserializeValue(deserializeCtx, ':for', component.directives?.[':for']).value
        : undefined;

      if (
        dynamicIf != undefined &&
        (dynamicIf.value == undefined || dynamicIf.value === true || dynamicIf.value === false)
      ) {
        dynamicIf = undefined;
      }

      if (
        dynamicDisplay != undefined &&
        (dynamicDisplay.value == undefined || dynamicDisplay.value === true || dynamicDisplay.value === false)
      ) {
        dynamicDisplay = undefined;
      }

      if (dynamicFor) {
        let { type, value } = dynamicFor;
        if (type === PropBindType.expression) {
          dynamicFor.value = value.replace(/^(.*?in\s)/, '');
        }
      }

      const data = deserializeAttributes(deserializeCtx, attributes);

      const { dataBinds, staticData } = splitDynamicMap(data);

      const componentInfo = componentsInfoMap[sourceKey];
      if (componentInfo?.compConfig) {
        (component.extra as any).compConfig = componentInfo?.compConfig;
      }
      if (componentInfo?.events) {
        (component.extra as any).emitEvents = componentInfo?.events.map((item) => item.name);
      } else if (componentInfo?.emitEvents) {
        (component.extra as any).emitEvents = componentInfo.emitEvents.map((item) => item.eventName);
      }

      // 生成 widgets/dataBinds
      if (!isSlot(component) && component.id) {
        let _visible = parseDirectiveIfAndDispaly(component.directives?.[':if']);
        let _display = parseDirectiveIfAndDispaly(component?.directives?.[':display']);

        if (component.type !== ('PAGE' as any)) {
          /**
           * TODO:
           * 原版中 _parentId 赋值
           *  _parentId: parentComponent
           *  ? isSlot(parentComponent)
           *    ? parentComponent.extra?.__parent?.id
           *   : parentComponent.id
           * : null,
           */
          let _parentId = null;
          if (parentComponent) {
            _parentId = parentComponent.id;
            if (parentComponent.type === EComponentType.PAGE || parentComponent.type === EComponentType.COMPLEX) {
              _parentId = parentComponent.extra?.__parent?.id;
            } else if (isSlot(parentComponent)) {
              _parentId = parentComponent.extra?.__parent?.id;
              const { isScopeSlot } = getScopeSlotMeta(parentComponent.extra.__parent, parentComponent.id, {
                componentsInfoMap,
              });
              if (isScopeSlot) {
                _parentId = `${parentComponent.extra.__parent?.id}.${parentComponent.id}`;
              }
            }
          }

          compWidgets[component.id] = {
            ...Object.entries(staticData).reduce((data, [propsKey, propsValue]) => {
              let value = propsValue;
              if (value && Array.isArray(value)) {
                value = value.map((_, index) => {
                  const block = attributes[propsKey]?.[index]; // 因为 value 里的是序列化过的，尤其是选区的是时候
                  const blockInstances = block?.selectableBlock?.listeners;
                  if (blockInstances) {
                    block.selectableBlock.listenerInstances = generateListnerInstances(ctx, blockInstances || [], {
                      isComposite,
                      looseError,
                      useScopeWidgetData: options.useScopeWidgetData === undefined ? true : options.useScopeWidgetData,
                    });
                    return block;
                  } else {
                    // 普通的非选区的，返回staticData 里的值
                    return _;
                  }
                });
              }
              data[propsKey] = value;
              return data;
            }, {}),
            /**
             * 此处新增key，需要同步定义 widgets.ts/widgetKeys 常量
             */
            _visible,
            _display,
            style: { ...processedCommonStyle },
            classList: className.split(/\s/).filter((item) => !!item),
            widgetType: sourceKey,
            _parentId,
            getConfig: () => {
              return componentInfo?.compConfig ?? {};
            },
            _waForKey: component.directives?.key,
          };
        }

        const processedDataBinds = [...dataBinds];
        const dynamicMetaList = [
          { property: '_visible', meta: dynamicIf },
          { property: '_display', meta: dynamicDisplay },
          { property: '_waFor', meta: dynamicFor },
          { property: 'style', meta: dynamicStyle },
          { property: 'classList', meta: dynamicClassList },
        ];

        for (let item of dynamicMetaList) {
          const { property, meta } = item;
          if (meta?.value) {
            processedDataBinds.push({
              type: meta.type,
              bindDataPath: meta.value,
              propertyPath: property,
            });
          }
        }

        if (processedDataBinds.length > 0) {
          compDataBinds[component.id] = generateDataBinds(ctx, processedDataBinds, {
            isComposite,
            looseError,
            key: `组件 ${component.id}`,
            useScopeWidgetData: options.useScopeWidgetData === undefined ? true : options.useScopeWidgetData,
          });
        }
      }

      (component as any).listenerInstances = generateListnerInstances(ctx, component.listeners || [], {
        isComposite,
        looseError,
        key: `组件事件 ${component.id}`,
        useScopeWidgetData: options.useScopeWidgetData === undefined ? true : options.useScopeWidgetData,
      });

      delete component.listeners;
      if (!isEmptyObj(attributes)) {
        // 组件双向绑定
        const syncProps = componentInputProps[sourceKey];
        if (syncProps) {
          Object.keys(syncProps).forEach((syncPropKey) => {
            const syncProp = syncProps[syncPropKey];
            let syncPropArr: ISyncProp[] = syncProp ?? [];

            // 统一转成数组处理
            if (!Array.isArray(syncProp)) {
              syncPropArr = [syncProp];
            } else {
              syncPropArr = syncPropArr;
            }

            syncPropArr.forEach(({ changeEvent, valueFromEvent }) => {
              // 双向绑定需要优先第一个执行，否则会出现输入框的拼音被打断的问题

              function withContext(context) {
                let instance = new runtime.Function(
                  'context',
                  `return function({ event, forItems }) {
                  const $for = forItems;
                  const $index = $for.forIndexes ? $for.forIndexes[$for.forIndexes.length - 1] : undefined;
                  const wid = ${isComposite ? 'this.widgets' : 'context.$page.widgets'}.${component.id};
                  const widgetData = $for.forIndexes && $for.forIndexes.length > 0 ? context.get(wid, forItems.forIndexes) : wid;
                  widgetData.${syncPropKey} = ${valueFromEvent}
                }`,
                )(context);

                return ctx.this ? instance.bind(this) : instance;
              }
              (component as any).listenerInstances.unshift({
                trigger: changeEvent,
                instanceFunction: withContext({ ...ctx, get: lodashGet }),
              });
            });
          });
        }
      }
    },
  );

  return {
    widgets: compWidgets,
    dataBinds: compDataBinds,
    componentSchema: componentSchemaJson,
  };
}

// convert data binds to functions for performance & simplicity
export function generateDataBinds(ctx: IGenerateContext, dataBinds: IDataBind[], options?: IGenerateOptions) {
  const dataBindFuncs: Record<string, Function> = {};
  const { useScopeWidgetData = true } = options || {};

  dataBinds.forEach((bind: IDataBind) => {
    if (!bind.bindDataPath) {
      return console.warn('bindDataPath error', bind.propertyPath);
    }
    let databindFunction = () => {};
    let funcCode = `function (){}`;
    const alias = `
const $for = forItems;
const $index = $for.forIndexes ? $for.forIndexes[$for.forIndexes.length - 1] : undefined;
`;
    try {
      if (bind.type === PropBindType.forItem) {
        funcCode = `function(forItems){ return forItems.${bind.bindDataPath} } `;
      } else if ((bind.type as any) === 'scope') {
        funcCode = `function(_, __, ___, ____, $scope, _____){ ${
          useScopeWidgetData
            ? 'return $scope.${bind.bindDataPath};'
            : `return ({__type: "scopedValue", getValue: function($scope){ return $scope.${bind.bindDataPath}}});`
        } }`;
      } else if (bind.type === PropBindType.expression) {
        if (options?.isComposite) {
          funcCode = `function(forItems, $context, $w) {${alias}; return (\n${
            bind.bindDataPath.replace(/;$/, '') || 'undefined'
          }\n);}`;
        } else {
          const code = bind.bindDataPath.replace(/;$/, '');
          const content = `${alias} return (\n${code || 'undefined'}\n)`;
          if ((!useScopeWidgetData && /\$scope\./.test(code)) || code === '$scope') {
            funcCode = `(forItems, event, $context, $w, $scope, params) => ({__type: "scopedValue", getValue: ($scope) => { ${content} }})`;
          } else {
            funcCode = `(forItems, event, $context, $w, $scope, params) => {${content}}`;
          }
        }
      } else if (bind.type === PropBindType.prop) {
        let { bindDataPath } = bind;
        const isNegated = bindDataPath.startsWith('!');
        if (isNegated) bindDataPath = bindDataPath.replace(/^!/, '');
        if (options?.isComposite) {
          // 复合组件在预览时其实就是page，所以沿用page的变量即可
          funcCode = `function() { return ${isNegated ? '!' : ''}$comp.props.data.${bindDataPath} }`;
        } else {
          // 复合组件在预览时其实就是page，所以沿用page的变量即可
          funcCode = `function() { return ${isNegated ? '!' : ''}$page.props.data.${bindDataPath} }`;
        }
      } else {
        const isGlobal = bind.bindDataPath.startsWith('global.');

        let returnValue = '';
        const PREFIX_MAP = {
          [PropBindType.state]: 'state',
          [PropBindType.computed]: 'computed',
          // [PropBindType.dataVar]: 'dataVar',
          [PropBindType.stateData]: 'dataset.state',
          [PropBindType.paramData]: 'dataset.params',
        };
        switch (bind.type) {
          case PropBindType.state:
          case PropBindType.computed:
          // case PropBindType.dataVar:
          case PropBindType.stateData:
          case PropBindType.paramData: {
            if (isGlobal) {
              returnValue = bind.bindDataPath.replace(/^global([.[])/, (_, $1) => `$app.${PREFIX_MAP[bind.type]}${$1}`);
            } else {
              if (options?.isComposite) {
                returnValue = bind.bindDataPath
                  .replace(/^comp-\w+([.[])/, (_, $1) => `$comp.${PREFIX_MAP[bind.type]}${$1}`)
                  .replace(/^\$\w+_\d+([.[])/, (_, $1) => `$comp.${PREFIX_MAP[bind.type]}${$1}`);
              } else {
                returnValue = bind.bindDataPath.replace(/^\w+([.[])/, (_, $1) => `$page.${PREFIX_MAP[bind.type]}${$1}`);
              }
            }
            funcCode = `function(){ return ${returnValue} }`;
            break;
          }
        }
      }

      const imports = Object.keys(ctx?.$app?.utils || {}).map((key) => {
        return `const ${key} = $app.utils["${key}"]`;
      });
      if (imports.length) {
        imports.push('');
      }

      try {
        databindFunction = new runtime.Function(
          'context',
          `const { $page, $app, $comp } = context || {};
          const app = $app;
          ${imports.join(';')}
          return ${funcCode}`,
        )(ctx);
      } catch (e) {
        let errorType = e instanceof SyntaxError ? '语法错误' : '错误';
        e.message = `${options.key ? `${options.key}.` : ''}${bind.propertyPath} 绑定${errorType}: ${e.message?.replace(
          /^Unexpected token/,
          '非法字符',
        )}`;
        throw e;
      }
      if (ctx.this) {
        databindFunction = databindFunction.bind(ctx.this);
      }
    } catch (e) {
      if (options?.looseError) {
        console.error('generate dynamic data error:', e);
        databindFunction = () => {
          throw e;
        };
      } else {
        throw e;
      }
    }
    dataBindFuncs[bind.propertyPath] = databindFunction;
  });
  return dataBindFuncs;
}

/**
 * dataset 特殊的绑定格式
 * 要求：入参分别为 app ， page
 * 绑定只有变量，参数，表达式的绑定
 */
export function generateDataBindForDataset(bind: IDataBind) {
  let expr = '';
  const { type, bindDataPath } = bind;
  if (type === PropBindType.static || !type) {
    expr = typeof bindDataPath === 'string' ? `'${bindDataPath}'` : JSON.stringify(bindDataPath);
  } else if (type === PropBindType.expression) {
    expr = bindDataPath;
  } else {
    const isGlobal = bindDataPath.startsWith('global.');
    const isCopositive = bindDataPath.startsWith('comp-') || bindDataPath.startsWith('$comp_');

    const PREFIX_MAP = {
      [PropBindType.state]: 'state',
      [PropBindType.computed]: 'computed',
      [PropBindType.dataVar]: 'dataVar',
      [PropBindType.stateData]: 'dataset.state',
      [PropBindType.paramData]: 'dataset.params',
    };
    switch (type) {
      case PropBindType.state:
      case PropBindType.computed:
      case PropBindType.dataVar:
      case PropBindType.stateData:
      case PropBindType.paramData: {
        if (isCopositive) {
          expr = bindDataPath
            .replace(/^comp-\w+/, `$comp.${PREFIX_MAP[type]}`)
            .replace(/^\$comp_\w+/, `$comp.${PREFIX_MAP[type]}`);
        } else {
          expr = bindDataPath
            .replace(/^\w+./, `${isGlobal ? 'app' : '$page'}.${PREFIX_MAP[type]}.`)
            .replace(/^comp-\w+/, `$comp.${PREFIX_MAP[type]}`)
            .replace(/^\$comp_\w+/, `$comp.${PREFIX_MAP[type]}`);
        }
      }
    }
  }
  return new runtime.Function(
    'app, $page, $w',
    `const $app = app;
    const imports = Object.keys(app && app.utils || {}).map(function (key) {
      return \`const \${key} = app.utils["\${key}"]\`;
    });
    if(imports.length) {
      imports.push('')
    }
    const body =  imports.join(';')+\`return (\n${expr || 'undefined'}\n)\`
   return new Function("", body)()`,
  );
}

export function generateListnerInstances(
  ctx: IGenerateContext,
  listenerInstances: (IPlatformListener & { jsCode?: string; noPropagation?: boolean })[],
  options: IGenerateOptions = {
    isComposite: false,
    key: '',
    useScopeWidgetData: true,
  },
) {
  const { isComposite = false } = options;
  return listenerInstances.map((listener) => {
    const generatedListener: {
      key: string;
      trigger: string;
      isCapturePhase: boolean;
      noPropagation?: boolean;
      instanceFunction: Function;
      data: any;
      dataBinds: any;
      sourceKey?: string;
    } = {
      key: listener.id,
      trigger: listener.eventName,
      isCapturePhase: !!listener.isCapturePhase,
      noPropagation: listener.noPropagation,
      instanceFunction: () => {},
      data: {},
      dataBinds: {},
    };
    if (listener.type === ActionType.Material) {
      const { name, module } = listener.handler || {};
      generatedListener.instanceFunction = new runtime.Function(
        'context',
        `const { $page, $app, actionsMap = {} } = context || {};
        const app = $app;
        const sourceKey = "${module}:${name}";
        return function(...args){ return actionsMap[sourceKey] &&  actionsMap[sourceKey].apply(null, args)} `,
      )(ctx);
    } else if (listener.type === ActionType.PropEvent) {
      if (isComposite) {
        generatedListener.instanceFunction = new runtime.Function(
          'context',
          `return function ({ data, event, forItems, $w }) {
            event.detail.data = data
            return context.$comp.props.emit('${
              // TODO: CHECK listener.handler.name 具体是什么
              listener.handler.name || listener.jsCode
            }', event.detail);}`,
        )(ctx);
      } else {
        generatedListener.instanceFunction = new runtime.Function(
          'context',
          `return function (...args) { return context.$page.props.events.${
            listener.handler?.name || listener.jsCode
          }.apply(null, args);}`,
        )(ctx);
      }
    } else if (listener.type === ActionType.Inline) {
      // 内联 handler
      const instance = new runtime.Function('{event, data, forItems, $w}', `return ${listener.jsCode}`);
      generatedListener.instanceFunction = instance.bind(isComposite ? ctx.$comp : ctx.$page);
    } else if (listener.type === ActionType.Platform) {
      let name = listener.handler?.name || listener.jsCode;
      generatedListener.sourceKey = `${listener.handler?.module || ''}:${listener.handler?.name || ''}`;
      let functionBody = '';
      switch (name) {
        case 'callNanoFlow': {
          functionBody = `const flow = context.$page.__internal__.$w[data.id]; return flow && flow.trigger(data.data)`;
          break;
        }
        case 'callQuery': {
          functionBody = `const query = context.$page.__internal__.$w[data.id]; return query && query[data.method](data.data)`;
          break;
        }
        case 'invoke': {
          functionBody = `return context.$app.invoke({...data, component: $w[data.component]})`;
          break;
        }
        case 'setCurrentPageParams': {
          functionBody = `return context.$app.utils.getCurrentPage().setParams(data.params);`;
          break;
        }
        default: {
          functionBody = ` return context.$app.${name}(data) `;
        }
      }
      generatedListener.instanceFunction = new runtime.Function(
        'context',
        `return function({data, $w}){ ${functionBody} } `,
      )(ctx);
    } else if (listener.type === ActionType.DataSource) {
      generatedListener.instanceFunction = new runtime.Function(
        'context',
        `return function({data}) {return context.$app.cloud.callDataSource(data)}`,
      )(ctx);
    } else {
      // Lowcode action(handler)
      if (isComposite) {
        generatedListener.instanceFunction = new runtime.Function(
          'context',
          `return context.$comp.handler.${listener.handler?.name || listener.jsCode}.bind(context.$comp)`,
        )(ctx);
      } else {
        let name = listener.handler?.name || listener.jsCode;
        generatedListener.instanceFunction = new runtime.Function('context', `return context.$page.handler.${name}`)(
          ctx,
        );
      }
    }

    const processedParams = deserializeDynamicMap(
      {
        page: isComposite ? undefined : { id: ctx.$page.id, type: EComponentType.PAGE },
      },
      listener.handler?.params,
    );

    const { staticData, dataBinds } = splitDynamicMap(processedParams);

    generatedListener.data = staticData;
    generatedListener.dataBinds = generateDataBinds(ctx, dataBinds, {
      ...options,
      key: `${options.key ? `${options.key}.` : ''}listener.${listener.eventName}`,
    });
    return generatedListener;
  });
}

export function generateDatasetQuery(
  schema: IPlatformDataset['query'],
  context: IQueryContext,
  options: IGenerateOptions,
): Record<string, Query> {
  const result = {};
  for (const key in schema) {
    result[key] = new Query({ schema: schema[key], context, options });
  }
  return result;
}

export function generateEventFlows(flows = [], context: IEventFlowContext, options: IGenerateOptions) {
  const result = {};
  for (let flow of flows) {
    result[flow.id] = new EventFlow({ schema: flow, context, options });
  }
  return result;
}
