import fmtEvent from './fmtEvent';

function removeNullProps(props) {
  const newProps = {};
  for (const key in props) {
    if (props[key] !== null) {
      newProps[key] = props[key];
    }
  }
  return newProps;
}

function buildProperties(props) {
  const newProperties = {};
  for (const key in props) {
    let type = null;
    switch (typeof props[key]) {
      case 'string':
        type = String;
        break;
      case 'number':
        type = Number;
        break;
      case 'boolean':
        type = Boolean;
        break;
    }
    newProperties[key] = {
      type,
      value: props[key],
    };
  }
  return newProperties;
}

function mergeDefaultProps(defaultProps: Record<string, any> = {}) {
  return {
    ...defaultProps,
  };
}

type ComponentInstance<Props, Methods, Data, Mixins, InstanceMethods> = unknown;

// 增加store使用的版本
interface IComponentOptions {
  // TODO: 需要考虑没有启用 Component2 的情况
  onInit?: () => void;
  didUnmount?: () => void;
}
type ExtendedInstanceMethods = Partial<IComponentOptions> & Record<string, any>;
export const ComponentWithSignalStoreImpl = <
  S,
  M extends Record<string, (o: { store: S }) => unknown>,
  Props,
  Methods = unknown,
  Data = unknown,
  Mixins = unknown,
  InstanceMethods extends ExtendedInstanceMethods = ExtendedInstanceMethods
>({
  storeOptions,
  props: defaultProps,
  methods,
  data,
  mixins,
  ...instanceMethods
}: {
  storeOptions?: TStoreOptions<S, M>;
  props?: Props;
  methods?: Methods;
  data?: Data;
  mixins?: Mixins & any;
} & InstanceMethods) => {
  const storeBinder = new StoreBinder(storeOptions);

  const defaultOnInit = function () {
    storeBinder.init(this as unknown as TStoreInitOptions<S>);
  };
  const instanceMethodsCopy: ExtendedInstanceMethods = { ...instanceMethods };

  // 确保 instanceMethods 存在
  // 备份原有的 onInit 和 didUnmount 方法
  const onInitBackup = instanceMethodsCopy.onInit || (() => {});
  const onDidUnmountBackup = instanceMethodsCopy.didUnmount || (() => {});

  instanceMethodsCopy.onInit = function () {
    defaultOnInit.call(this);
    if (onInitBackup) {
      onInitBackup.call(this);
    }
  };

  instanceMethodsCopy.didUnmount = function () {
    if (onDidUnmountBackup) {
      onDidUnmountBackup.call(this);
    }
    storeBinder.dispose();
  };

  // 这里确保 instanceMethodsCopy.onInit 被正确执行
  if (!instanceMethodsCopy.onInit) {
    instanceMethodsCopy.onInit = defaultOnInit;
  }



  Component({
    props: removeNullProps(mergeDefaultProps(defaultProps)),
    methods,
    mixins: mixins || [],
    data,
    ...(instanceMethodsCopy || {}),
  });
};

type TMapState<S> = Record<string, (o: { store: S }) => unknown>;

// 这里类型直接抄了 PageWithAnyStore，但其实对于 antd-mini 的场景，store 和 updateHook 都可以写死而不需要使用时自定义
export type TStoreOptions<S, M extends TMapState<S>> = {
  /**
   * store 的创建器，因为页面会有多实例，所以 store 必须每个页面实例单独创建一次
   * 如果你非要多个实例共用一个 store，那你可以 const store = new Store(); store: => store;
   */
  store: () => S;
  /**
   * store 数据更新后的 listener，通过它来触发向页面数据的同步，返回值是一个 dispose 函数。
   * 在 mobx 是 autorun、redux 是 subscribe、你要不在意性能，setInterval 也可以
   */
  updateHook: (fn: () => void) => () => void;
  /**
   * store 数据到页面 data 的映射关系
   */
  mapState: M;
};

export type TStoreInitOptions<S> = {
  setData: (o: Record<string, unknown>, callback?: () => void) => void;
  $store?: S;
};

export class StoreBinder<S, M extends TMapState<S>> {
  private disposeStore?: () => void = undefined;
  constructor(private storeOptions: TStoreOptions<S, M>) {}

  /**
   * 绑定和 store 的关系
   */
  init(theThis: TStoreInitOptions<S>) {
    const store = this.storeOptions.store();
    const disposes = Object.keys(this.storeOptions.mapState).map((key) => {
      return this.storeOptions.updateHook(() => {
        theThis.setData({
          [key]: this.storeOptions.mapState[key]({ store }),
        });
      });
    });
    theThis.$store = store;

    this.disposeStore = () => disposes.forEach((d) => d());
  }

  /**
   * 释放和 store 的关系
   */
  dispose() {
    if (this.disposeStore) {
      this.disposeStore();
    }
  }
}

function ComponentImpl<
  Props,
  Data = unknown,
  Methods = unknown,
  Mixins = unknown,
  InstanceMethods = unknown
>({
  props: defaultProps,
  data,
  methods,
  mixins,
  ...instanceMethods
}: {
  props?: Props;
  data?: Data;
  methods?: Methods;
  mixins?: Mixins & any;
} & InstanceMethods) {

  Component({
    props: removeNullProps(mergeDefaultProps(defaultProps)),
    methods,
    mixins: mixins || [],
    data,
    ...instanceMethods,
  });
}

export interface IPlatformEvent {
  currentTarget: {
    dataset: Record<string, unknown>;
  };
  target: {
    dataset: Record<string, unknown>;
  };
}

export function triggerEvent(
  instance: any,
  eventName: string,
  value: unknown,
  e?: any
) {
  // 首字母大写，然后加上 on

  const alipayCallbackName =
    'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
  const props = instance.props;
  if (props[alipayCallbackName]) {
    props[alipayCallbackName](value, fmtEvent(props, e));
  }

}

export function triggerEventOnly(instance: any, eventName: string, e?: any) {
  // 首字母大写，然后加上 on

  const alipayCallbackName =
    'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
  const props = instance.props;
  if (props[alipayCallbackName]) {
    props[alipayCallbackName](fmtEvent(props, e));
  }

}

export function triggerEventValues(
  instance: any,
  eventName: string,
  values: any[],
  e?: any
) {
  // 首字母大写，然后加上 on

  const alipayCallbackName =
    'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
  const props = instance.props;
  if (props[alipayCallbackName]) {
    props[alipayCallbackName](...values, fmtEvent(props, e));
  }

}

export function triggerCatchEvent(instance: any, eventName: string, e?: any) {
  const props = instance.props;
  if (props[eventName]) {
    props[eventName](fmtEvent(props, e));
  }

}

export function getValueFromProps(instance: any, propName?: string | string[]) {
  let value;
  const props = instance.props;
  if (!propName) {
    return props;
  }
  if (typeof propName === 'string') {
    value = props[propName];
  }
  if (Array.isArray(propName)) {
    value = propName.map((name) => props[name]);
  }


  return value;
}

export {
  ComponentWithSignalStoreImpl as ComponentWithSignalStore,
  ComponentImpl as Component,
};
