import * as React from 'react';

import { camelcase, isEmptyObj, isSlot } from '../common';
import {
  translateStyleToRem,
  translateStyleToRpx,
  _handleStyleNumValue,
  setStyleValue,
  setDistanceStyle,
} from '../style';

import {
  IComponent,
  IPageComponent,
  IPlatformApp,
  IPlatformListener,
  IBasicComponent,
  IComplexComponent,
  deserializeAttributes,
  deserializeValue,
  PropBindType,
  ActionType,
  IDataBind,
  processComponentModule,
  serializeDynamicMap,
  deserializeDynamicMap,
  EComponentType,
  IPlatformComponent,
} from '@cloudbase/cals';
import {
  uniq as _uniq,
  map as _map,
  get as lodashGet,
  cloneDeep,
} from 'lodash-es';

/**
 * utils start
 */

function getMetaInfoBySourceKey(sourceKey: string) {
  if (!sourceKey) {
    return {};
  }
  const [materialName, name] = sourceKey.split(':');
  return {
    materialName,
    name,
    variableName: camelcase(materialName + '_' + name),
  };
}

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

// Convert WeApps common style to css styles(React.CSSProperties)
export function toCssStyle(
  commonStyle: any = {},
  options: {
    toRem: boolean;
    ignoreSelf?: boolean;
    addPXUnit?: boolean;
    toRpx?: boolean;
  } = { toRem: true, ignoreSelf: false, addPXUnit: false, toRpx: false }
): React.CSSProperties {
  const {
    size,
    // transform,
    text,
    border,
    background,
    margin,
    padding,
    zIndex,
    position,
    display,
    flexConfig,
    custom,
    self,
  } = commonStyle;

  const style: React.CSSProperties = {};

  if (size) {
    setStyleValue(
      style,
      'width',
      _handleStyleNumValue(size.width, !!options.addPXUnit)
    );
    setStyleValue(
      style,
      'height',
      _handleStyleNumValue(size.height, !!options.addPXUnit)
    );
  }

  // if (transform) {
  //   const { rotate, opacity, scale, radius } = transform
  //   if ((rotate && rotate !== 0) || (scale && scale !== 1)) {
  //     style.transform = `${rotate ? `rotate(${rotate}deg) ` : ''}${scale ? `scale(${scale})` : ''}`
  //   }
  //   setStyleValue(style, 'opacity', opacity)
  //   setStyleValue(style, 'borderRadius', radius)
  // }

  if (margin) {
    setDistanceStyle(style, margin, 'margin', !!options.addPXUnit);
  }

  if (padding) {
    setDistanceStyle(style, padding, 'padding', !!options.addPXUnit);
  }

  if (display) {
    setStyleValue(style, 'display', display);
  }

  if (display === 'flex' && flexConfig) {
    if (flexConfig.justifyContent)
      setStyleValue(style, 'justifyContent', flexConfig.justifyContent);
    if (flexConfig.alignItems)
      setStyleValue(style, 'alignItems', flexConfig.alignItems);
    if (flexConfig.flexWrap && flexConfig.flexDirection) {
      setStyleValue(
        style,
        'flexFlow',
        `${flexConfig.flexDirection} ${flexConfig.flexWrap}`
      );
    } else {
      if (flexConfig.flexWrap)
        setStyleValue(style, 'flexWrap', flexConfig.flexWrap);
      if (flexConfig.flexDirection)
        setStyleValue(style, 'flexDirection', flexConfig.flexDirection);
    }
  }

  if (typeof zIndex === 'number' || `${zIndex}`.search(/^\d+$/) === 0) {
    style.zIndex = zIndex;
  }

  if (text) {
    setStyleValue(style, 'color', text.color);
    setStyleValue(
      style,
      'fontSize',
      _handleStyleNumValue(text.fontSize, !!options.addPXUnit)
    );
    setStyleValue(style, 'lineHeight', text.lineHeight);
    setStyleValue(style, 'textAlign', text.textAlign);
    setStyleValue(style, 'fontWeight', text.weight);
    if (text.opacity) {
      setStyleValue(style, 'opacity', text.opacity / 100);
    }
  }

  if (border) {
    const { type, color, width, radius, radiusInfo } = border;
    if (width !== undefined) {
      if (type) {
        setStyleValue(
          style,
          'border',
          `${_handleStyleNumValue(width, !!options.addPXUnit)} ${type} ${
            color || ''
          }`
        );
      } else {
        setStyleValue(
          style,
          'borderWidth',
          _handleStyleNumValue(width, !!options.addPXUnit)
        );
        if (color) setStyleValue(style, 'borderColor', color);
      }
    }
    if (radius !== undefined) {
      setStyleValue(
        style,
        'borderRadius',
        _handleStyleNumValue(radius, !!options.addPXUnit)
      );
    }
    if (radiusInfo && !isEmptyObj(radiusInfo)) {
      if (Object.keys(radiusInfo).length === 4) {
        if (_uniq(_map(radiusInfo, (val) => val)).length === 1) {
          // 4个值全相等的情况
          setStyleValue(
            style,
            'borderRadius',
            _handleStyleNumValue(radiusInfo.topLeft, !!options.addPXUnit)
          );
        } else if (
          radiusInfo.topLeft === radiusInfo.bottomRight &&
          radiusInfo.topRight === radiusInfo.bottomLeft
        ) {
          // 俩俩相等的情况
          setStyleValue(
            style,
            'borderRadius',
            `${_handleStyleNumValue(
              radiusInfo.topLeft,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.topRight,
              !!options.addPXUnit
            )}`
          );
        } else if (radiusInfo.topRight === radiusInfo.bottomLeft) {
          setStyleValue(
            style,
            'borderRadius',
            `${_handleStyleNumValue(
              radiusInfo.topLeft,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.topRight,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.bottomRight,
              !!options.addPXUnit
            )}`
          );
        } else {
          setStyleValue(
            style,
            'borderRadius',
            `${_handleStyleNumValue(
              radiusInfo.topLeft,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.topRight,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.bottomRight,
              !!options.addPXUnit
            )} ${_handleStyleNumValue(
              radiusInfo.bottomLeft,
              !!options.addPXUnit
            )}`
          );
        }
      } else {
        setStyleValue(
          style,
          'borderTopLeftRadius',
          _handleStyleNumValue(radiusInfo.topLeft, !!options.addPXUnit)
        );
        setStyleValue(
          style,
          'borderTopRightRadius',
          _handleStyleNumValue(radiusInfo.topRight, !!options.addPXUnit)
        );
        setStyleValue(
          style,
          'borderBottomRightRadius',
          _handleStyleNumValue(radiusInfo.bottomRight, !!options.addPXUnit)
        );
        setStyleValue(
          style,
          'borderBottomLeftRadius',
          _handleStyleNumValue(radiusInfo.bottomLeft, !!options.addPXUnit)
        );
      }
    }
  }

  if (background) {
    const { bgType, color, image, size, repeat, position, positionObj } =
      background;
    if (bgType === 'color') {
      setStyleValue(style, 'background', color);
    } else if (bgType === 'image') {
      // 如 radial-gradient(crimson, skyblue);
      if (image != null) {
        if (image.search(/[()]/) >= 0) {
          style.background = image;
        } else {
          style.background = `url(${image})`;
        }
      }
      if (repeat) style.background += ` ${repeat}`;
      if (size) {
        setStyleValue(
          style,
          'backgroundSize',
          _handleStyleNumValue(size, !!options.addPXUnit)
        );
      }
      setStyleValue(style, 'backgroundPosition', position);
      if (positionObj && !isEmptyObj(positionObj)) {
        style.background += ` ${_handleStyleNumValue(
          positionObj.left,
          !!options.addPXUnit
        )} ${_handleStyleNumValue(positionObj.top, !!options.addPXUnit)}`;
      }
    }

    // FIXME: 这里兼容原有应用的数据，后面应去掉
    if (bgType === undefined) {
      setStyleValue(style, 'backgroundColor', color);
      if (image != null) {
        style.backgroundImage = `url(${image})`;
        setStyleValue(style, 'backgroundRepeat', repeat);
        setStyleValue(
          style,
          'backgroundSize',
          _handleStyleNumValue(size, !!options.addPXUnit)
        );
        setStyleValue(style, 'backgroundPosition', position);
      }
    }
  }

  if (position) {
    setStyleValue(style, 'position', position.position);
    if (position.left !== undefined) {
      setStyleValue(
        style,
        'left',
        _handleStyleNumValue(position.left, !!options.addPXUnit)
      );
    }
    if (position.right !== undefined) {
      setStyleValue(
        style,
        'right',
        _handleStyleNumValue(position.right, !!options.addPXUnit)
      );
    }
    if (position.top !== undefined) {
      setStyleValue(
        style,
        'top',
        _handleStyleNumValue(position.top, !!options.addPXUnit)
      );
    }
    if (position.bottom !== undefined) {
      setStyleValue(
        style,
        'bottom',
        _handleStyleNumValue(position.bottom, !!options.addPXUnit)
      );
    }
  }

  if (custom && custom.length > 0) {
    custom.map((item) => {
      setStyleValue(style, item.key, item.value);
    });
  }

  if (self && !options.ignoreSelf) {
    Object.assign(style, self);
  }

  if (options.toRpx) {
    return translateStyleToRpx(style);
  }

  return options.toRem ? translateStyleToRem(style) : style;
}

export function getComponentsInfo(dependencies) {
  const outputObj = {};
  dependencies.forEach(({ name: materialName, components, isComposite }) => {
    if (isComposite) {
      components.forEach((component) => {
        let compItem = component;
        const sourceKey = `${materialName}:${compItem.name}`;
        outputObj[sourceKey] = {
          isComposite,
          ...compItem,
        };
      });
    } else {
      components.forEach(
        ({
          name,
          meta,
          emitEvents,
          events = undefined,
          isContainer,
          compConfig,
        }) => {
          outputObj[materialName + ':' + name] = {
            isComposite,
            ...meta,
            events: events,
            emitEvents: emitEvents,
            isContainer,
            compConfig,
          };
        }
      );
    }
  });
  return outputObj;
}

function getInputProps(componentsMetaMap) {
  const outputObj = {};
  for (let key in componentsMetaMap) {
    let component = componentsMetaMap[key];
    const sourceKey = key;
    if (component.isComposite) {
      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] || {}),
          };
        }
      });
    } else {
      const inputProps = component?.inputProps || component?.syncProps;
      if (inputProps) {
        outputObj[sourceKey] = inputProps;
      }
    }
  }
  return outputObj;
}

/**
 * utils end
 */

function splitDynamicMap(data) {
  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;
}

interface IGenerateOptions {
  isComposite?: boolean;
  looseError?: boolean;
}

function walkThroughCals<P>(
  cals,
  process: (
    item: P & {
      extra: Required<IPlatformComponent>['extra'] & {
        __parent;
      };
    }
  ) => void
) {
  let { items = [] } = cals;
  cals.extra = cals.extra || {};
  if (cals?.directives?.[':if'] === false) {
    cals.items = [];
  }
  for (let item of items) {
    item.extra = item.extra || {};
    item.extra.__parent = cals;
    walkThroughCals(item, process);
  }
  process(cals);
  return cals;
}

export function getComponentSchema(
  ctx: {
    app;
    $app;
    $page;
    this;
  },
  page: IPageComponent,
  isComposite = false,
  componentsInfoMap: { [componentSourceKey: string]: any } = {},
  wrapperClass?: string,
  options?: {
    isComposite?: boolean;
    looseError?: boolean;
  }
) {
  const componentInputProps = getInputProps(componentsInfoMap) || {};
  const copyJson = JSON.parse(JSON.stringify(page));
  const compWidgets = {};
  const compDataBinds = {};

  const componentSchemaJson = walkThroughCals<
    IBasicComponent | IComplexComponent
  >(copyJson, (component) => {
    if (isSlot(component)) {
      return;
    }
    const { component: componentName, module, attributes = {} } = component;
    const { commonStyle = {} } = component.extra || {};
    const processedCommonStyle = attributes.style
      ? attributes.style
      : toCssStyle(commonStyle);
    if (processedCommonStyle && !isEmptyObj(processedCommonStyle)) {
      component.extra.commonStyle = processedCommonStyle;
    }
    const sourceKey = `${module}:${componentName}`;
    const { class: className = '' } = attributes;

    const deserializeCtx = { page };

    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 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 (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 as any)?.compConfig) {
      (component.extra as any).compConfig = (componentInfo as any)?.compConfig;
    }
    if ((componentInfo as any)?.events) {
      (component.extra as any).emitEvents = (componentInfo as any)?.events.map(
        (item) => item.name
      );
    } else if ((componentInfo as any)?.emitEvents) {
      (component.extra as any).emitEvents = (
        componentInfo as any
      ).emitEvents.map((item) => item.eventName);
    }

    // 生成 widgets/dataBinds
    if (!isSlot(component) && component.id) {
      const parentComponent = component.extra.__parent;
      let _visible = undefined;
      switch (component.directives?.[':if']) {
        case true:
        case false: {
          _visible = component.directives?.[':if'];
          break;
        }
        case undefined: {
          _visible = true;
          break;
        }
      }
      compWidgets[component.id] = {
        ...Object.entries(staticData).reduce(function (
          data,
          [propsKey, propsValue]
        ) {
          let value = propsValue;
          if (value && Array.isArray(value)) {
            value = value.map((_, index) => {
              const block = attributes?.[propsKey]?.[index];
              const blockInstances = block?.selectableBlock?.listeners;
              if (blockInstances) {
                block.selectableBlock['listenerInstances'] =
                  generateListnerInstances(ctx, blockInstances || [], {
                    isComposite,
                    looseError: options?.looseError,
                  });
              }
              return block;
            });
          }
          data[propsKey] = value;
          return data;
        },
        {}),
        _visible,
        style: { ...processedCommonStyle },
        classList: className.split(/\s/).filter((item) => !!item),
        widgetType: sourceKey,
        _parentId: parentComponent
          ? isSlot(parentComponent)
            ? parentComponent.extra?.__parent?.id
            : parentComponent.id
          : null,
        getConfig: () => {
          return (componentInfo as any)?.compConfig ?? {};
        }
      };

      const processedDataBinds = [...dataBinds];
      if (dynamicIf?.value) {
        processedDataBinds.push({
          type: dynamicIf.type,
          bindDataPath: dynamicIf.value,
          propertyPath: '_visible',
        });
      }
      if (dynamicFor?.value) {
        processedDataBinds.push({
          type: dynamicFor.type,
          bindDataPath: dynamicFor.value,
          propertyPath: '_waFor',
        });
      }
      if (dynamicStyle?.value) {
        processedDataBinds.push({
          type: dynamicStyle.type,
          bindDataPath: dynamicStyle.value,
          propertyPath: 'style',
        });
      }
      if (dynamicClassList) {
        processedDataBinds.push({
          type: dynamicClassList.type,
          bindDataPath: dynamicClassList.value,
          propertyPath: 'classList',
        });
      }

      if (processedDataBinds.length > 0) {
        compDataBinds[component.id] = generateDataBinds(
          ctx,
          processedDataBinds,
          { isComposite, looseError: options?.looseError }
        );
      }
    }

    component['listenerInstances'] = generateListnerInstances(
      ctx,
      component.listeners || [],
      { isComposite, looseError: options?.looseError }
    );

    delete component.listeners;
    if (!isEmptyObj(attributes)) {
      // 如果是复合组件的根节点，则补充 wrapperClass
      if (isComposite) {
        if (!component?.extra?.__parent?.extra?.__parent) {
          component.attributes = {};
          component.attributes.class = `${component.attributes.class} ${wrapperClass}`;
        }
      }

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

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

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

            function withContext(context) {
              let instance = new Function(
                'context',
                `return ({ event, forItems }) => {
                  const $for = forItems;
                  const wid = ${
                    isComposite ? 'this.widgets' : '$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['listenerInstances'].unshift({
              trigger: changeEvent,
              instanceFunction: withContext({ get: lodashGet }),
            });
          });
        });
      }
    }
  });

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

// convert data binds to functions for performance & simplicity
export function generateDataBinds(
  ctx,
  dataBinds: IDataBind[],
  options?: IGenerateOptions
) {
  const dataBindFuncs = {};
  dataBinds.forEach((bind: IDataBind) => {
    if (!bind.bindDataPath) {
      return console.warn('无 bindDataPath', bind.propertyPath);
    }
    let funcCode = () => {};
    try {
      if (bind.type === PropBindType.forItem) {
        funcCode = new Function(
          'context',
          `return (forItems) => forItems.${bind.bindDataPath}`
        )(ctx);
      } else if ((bind.type as any) === 'scope') {
        funcCode = new Function(
          'context',
          `return () => ({__type: "scopedValue", getValue: ($scope)=>$scope.${bind.bindDataPath}})}`
        )(ctx);
      } else if (bind.type === PropBindType.expression) {
        if (options?.isComposite) {
          funcCode = new Function(
            'context',
            `const { $page, app, $app } = context || {};
            return (forItems, $context) => {const $for = forItems; return (\n${
              bind.bindDataPath
                .replace(/\$comp/g, 'this.$WEAPPS_COMP')
                .replace(/;$/, '') || 'undefined'
            }\n)}`
          )(ctx);
        } else {
          const code = bind.bindDataPath.replace(/;$/, '');
          funcCode = new Function(
            'context',
            `const { $page, app, $app } = context || {};

            return ${
              /\$scope\./.test(code)
                ? `(forItems, event, $context) => ({__type: "scopedValue", getValue: ($scope) => { const $for = forItems; return (\n${code}\n)}})`
                : `(forItems, event, $context) => { const $for = forItems; return (\n${
                    code || 'undefined'
                  }\n)}`
            }`
          )(ctx);
        }
      } else if (bind.type === PropBindType.prop) {
        let bindDataPath = bind.bindDataPath;
        const isNegated = bindDataPath.startsWith('!');
        if (isNegated) bindDataPath = bindDataPath.replace(/^!/, '');
        if (options?.isComposite) {
          // 复合组件在预览时其实就是page，所以沿用page的变量即可
          funcCode = new Function(
            'context',
            `const { $page, app, $app } = context || {};
            return () => ${
              isNegated ? '!' : ''
            }${'this.$WEAPPS_COMP'}.props.data.${bindDataPath}`
          )(ctx);
        } else {
          // 复合组件在预览时其实就是page，所以沿用page的变量即可
          funcCode = new Function(
            'context',
            `const { $page, app, $app } = context || {};
            return () => ${
              isNegated ? '!' : ''
            }$page.props.data.${bindDataPath}`
          )(ctx);
        }
      } 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./,
                `app.${PREFIX_MAP[bind.type]}.`
              );
            } else {
              if (options?.isComposite) {
                returnValue = bind.bindDataPath
                  .replace(
                    /^comp-\w+./,
                    `this.$WEAPPS_COMP.${PREFIX_MAP[bind.type]}.`
                  )
                  .replace(
                    /^\$\w+_\d+./,
                    `this.$WEAPPS_COMP.${PREFIX_MAP[bind.type]}.`
                  );
              } else {
                returnValue = bind.bindDataPath.replace(
                  /^\w+./,
                  `$page.${PREFIX_MAP[bind.type]}.`
                );
              }
            }
            funcCode = new Function(
              'context',
              `const { $page, app, $app } = context || {};
              return () => ${returnValue}`
            )(ctx);
            break;
          }
        }
      }
    } catch (e) {
      if (options?.looseError) {
        console.error('generate dynamic data error:', e);
        funcCode = () => {
          throw e;
        };
      } else {
        throw e;
      }
    }
    dataBindFuncs[bind.propertyPath] = funcCode;
  });
  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 Function(
    'app, $page',
    `const $app = app; return (\n${expr || 'undefined'}\n)`
  );
}

function generateListnerInstances(
  ctx,
  listenerInstances: IPlatformListener[],
  options: IGenerateOptions = {
    isComposite: false,
  }
) {
  const { isComposite = false } = options;
  return listenerInstances.map((listener) => {
    const generatedListener: {
      key: string;
      trigger: string;
      isCapturePhase: boolean;
      noPropagation?: boolean;
      instanceFunction: Function;
      data: any;
      dataBinds: any;
    } = {
      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 Function(
        'context',
        `const { $page, app, $app, actionsMap = {} } = context || {};
        return (...args) => actionsMap["${module}:${name}"]?.apply?.(null, args)`
      )(ctx);
    } else if (listener.type === ActionType.PropEvent) {
      if (isComposite) {
        const instance = new Function(
          '{ data, event, forItems }',
          `
          event.detail.data = data;
          this.props.emit('${
            // TODO: CHECK listener.handler.name 具体是什么
            listener.handler.name || listener['jsCode']
          }', event.detail);`
        );
        generatedListener.instanceFunction = ctx.this
          ? instance.bind(this)
          : instance;
      } else {
        generatedListener.instanceFunction = new Function(
          'context',
          `return (...args) => context.$page.props.events.${
            listener.handler?.name || listener['jsCode']
          }.apply(null, args)`
        )(ctx);
      }
    } else if (listener.type === ActionType.Inline) {
      // 内联 handler
      const instance = new Function(
        '{event, domEvent, customEventData, data, forItems}',
        `return ${listener['jsCode']}`
      );
      if (isComposite) {
        generatedListener.instanceFunction = ctx.this
          ? instance.bind(this)
          : instance;
      } else {
        generatedListener.instanceFunction = instance.bind(ctx.$page);
      }
    } else if (listener.type === ActionType.Platform) {
      let name = listener.handler?.name || listener['jsCode'];
      generatedListener.instanceFunction = new Function(
        '{data}',
        `return app.${name}(data)`
      );
    } else if (listener.type === ActionType.DataSource) {
      generatedListener.instanceFunction = new Function(
        '{data}',
        `return app.cloud.callDataSource(data)`
      );
    } else {
      // Lowcode action(handler)
      if (isComposite) {
        generatedListener.instanceFunction = new Function(
          '',
          `return this.handler.${
            listener.handler?.name || listener['jsCode']
          }.bind(this)`
        ).bind(ctx.this)();
      } else {
        let name = listener.handler?.name || listener['jsCode'];
        generatedListener.instanceFunction = new Function(
          'context',
          `return context.$page.handler.${name}`
        )(ctx);
      }
    }

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

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

    generatedListener.data = staticData;
    generatedListener.dataBinds = generateDataBinds(ctx, dataBinds, options);
    return generatedListener;
  });
}
