import React, { CSSProperties } from 'react';
import {Renderer, RendererEnv, RendererProps} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service';
import {Api, SchemaNode, PlainObject, Payload} from '../types';
import {set16ToRgb} from '../utils/helper';
import cx from 'classnames';
import {
  BaseSchema,
  SchemaApi,
  SchemaTokenizeableString,
  SchemaTpl
} from '../Schema';
import {withStore} from '../components/WithStore';
import {flow, Instance, types} from 'mobx-state-tree';
import {getPropValue, getVariable, guid, isObject} from '../utils/helper';
import {StoreNode} from '../store/node';
import isPlainObject from 'lodash/isPlainObject';
import {isPureVariable, resolveVariableAndFilter} from '../utils/tpl-builtin';
import {
  buildApi,
  isApiOutdated,
  isEffectiveApi,
  normalizeApi
} from '../utils/api';

enum STYLE_ENUM {
  'STYLE1' = '1',// 直角 字color 背景带点透明color
  'STYLE2' = '2',// 直角 文字白色 背景用color
  'STYLE3' = '3',// 圆角 背景白色 文字和边框用color
  'STYLE4' = '4',// 圆角 文字白色 背景用color
}

/**
 * Mapping 映射展示控件。
 */
export interface MappingSchema extends BaseSchema {
  /**
   * 指定为映射展示控件
   */
  type: 'map' | 'mapping';

  /**
   * 关联字段名。
   */
  name?: string;

  /**
   * 配置映射规则，值可以使用模板语法。当 key 为 * 时表示 else，也就是说值没有映射到任何规则时用 * 对应的值展示。
   */
  map?: {
    [propName: string]: SchemaTpl;
  };

  // 是否显示icon
  showIcon?: boolean;

  /**
   * 如果想远程拉取字典，请配置 source 为接口。
   */
  source?: SchemaApi | SchemaTokenizeableString;

  /**
   * 占位符
   */
  placeholder?: string;
}

export const Store = StoreNode.named('MappingStore')
  .props({
    fetching: false,
    errorMsg: '',
    map: types.frozen<{
      [propName: string]: any;
    }>({})
  })
  .actions(self => {
    const load: (env: RendererEnv, api: Api, data: any) => Promise<any> = flow(
      function* (env, api, data) {
        try {
          self.fetching = true;
          const ret: Payload = yield env.fetcher(api, data);

          if (ret.ok) {
            const data = ret.data || {};
            (self as any).setMap(data);
          } else {
            throw new Error(ret.msg || 'fetch error');
          }
        } catch (e) {
          self.errorMsg = e.message;
        } finally {
          self.fetching = false;
        }
      }
    );

    return {
      load,
      setMap(options: any) {
        if (isObject(options)) {
          self.map = {
            ...options
          };
        }
      }
    };
  });

export type IStore = Instance<typeof Store>;

export interface MappingProps
  extends Omit<RendererProps, 'store'>,
    Omit<MappingSchema, 'type' | 'className'> {
  store: IStore;
}

export const MappingField = withStore(props =>
  Store.create(
    {
      id: guid(),
      storeType: Store.name
    },
    props.env
  )
)(
  class extends React.Component<MappingProps, object> {
    static defaultProps: Partial<MappingProps> = {
      placeholder: '-',
      map: {},
      showIcon: false
    };

    constructor(props: MappingProps) {
      super(props);

      props.store.syncProps(props, undefined, ['map']);
    }

    componentDidMount() {
      const {store, source, data} = this.props;

      this.reload();
    }

    componentDidUpdate(prevProps: MappingProps) {
      const props = this.props;
      const {store, source, data} = this.props;

      store.syncProps(props, prevProps, ['map']);

      if (isPureVariable(source)) {
        const prev = resolveVariableAndFilter(
          prevProps.source as string,
          prevProps.data,
          '| raw'
        );
        const curr = resolveVariableAndFilter(source as string, data, '| raw');

        if (prev !== curr) {
          store.setMap(curr);
        }
      } else if (
        isApiOutdated(
          prevProps.source,
          props.source,
          prevProps.data,
          props.data
        )
      ) {
        this.reload();
      }
    }

    reload() {
      const {source, data, env} = this.props;
      const store = this.props.store;
      if (isPureVariable(source)) {
        store.setMap(resolveVariableAndFilter(source, data, '| raw'));
      } else if (isEffectiveApi(source, data)) {
        const api = normalizeApi(source, 'get');
        api.cache = api.cache ?? 30 * 1000;
        store.load(env, api, data);
      }
    }

    render() {
      const {
        className,
        placeholder,
        render,
        classnames: cx,
        name,
        data,
        showIcon,
        store
      } = this.props;
      const map = store.map;

      let key = getPropValue(this.props);

      let viewValue: React.ReactNode = (
        <span className="text-muted">{placeholder}</span>
      );

      key = typeof key === 'string' ? key.trim() : key; // trim 一下，干掉一些空白字符。
      let value: any = undefined;

      if(typeof key === 'undefined') return viewValue;

      if(typeof key === 'string' && ~key.indexOf(',')){// 多个mapping一起显示的 不支持label模式
        viewValue = key.split(',').map((v: any)=>{
          if (
            map &&
            (value =
              map[v] ??
              (v === true && map['1']
                ? map['1']
                : v === false && map['0']
                ? map['0']
                : map['*'] || v)) !== undefined
          ) {
            return render('tpl', typeof value === 'object'? value.value: value);
          }
          return null
        }).filter((v: React.ReactNode | null)=> v)

        viewValue = React.Children.map(viewValue, (v: any, i)=>{
          let _clone = React.cloneElement(v, {
            key: i
          })
          if(i < (viewValue as []).length-1){
            return <>{_clone},</>
          }
          return _clone
        })

        return <span className={cx('MappingField', className)}>{viewValue}</span>;
      }
      
      if (
        map &&
        (value =
          map[key] ??
          (key === true && map['1']
            ? map['1']
            : key === false && map['0']
            ? map['0']
            : map['*'] || key)) !== undefined
      ) {
        let isObj = typeof value === 'object';
        viewValue = render('tpl', isObj? value.value: value);

        if(isObj && value.style){
          let { icon, color, style } = value;
          color = color || '#9b9b9b';

          let cssStyle: CSSProperties = {}
        
          if(style === STYLE_ENUM.STYLE1){
            cssStyle.color = color
            cssStyle.background = set16ToRgb(color, 0.2)
          }else if(style === STYLE_ENUM.STYLE2){
            cssStyle.color = '#ffffff';
            cssStyle.background = color;
          }else if(style === STYLE_ENUM.STYLE3){
            cssStyle.color = color
            cssStyle.border = `1px solid ${color}`
            cssStyle.background = 'none !important'
            cssStyle.borderRadius = '0.5rem'
          }else if(style === STYLE_ENUM.STYLE4){
            cssStyle.color = '#ffffff';
            cssStyle.border = `1px solid ${color}`
            cssStyle.background = color;
            cssStyle.borderRadius = '0.5rem'
          }

          return <span className={cx('MappingField', 'label', className)} style={cssStyle}>{
            showIcon && icon ?<MappingIcon src={icon} alt={value.value} style={{width: 12, height: 12, verticalAlign: "middle", marginRight: 3}}/>:null
          }{viewValue}</span>;
        }else{
          return <span className={cx('MappingField', className)}>{viewValue}</span>;
        }
      }

    }
  }
);

const MappingIcon: React.FC<{
  src: string
  alt?: string
  style?: CSSProperties
}> = (props) => {
  const {
    src = '',
    alt = '',
    style = undefined
  } = props;

  const [error, setError] = React.useState(false);

  // 加载失败
  const onError = (obj: any) => {
    setError(true)
  };

  return error? null: (
    <img
      src={src}
      alt={alt}
      style={style}
      onError={onError}
    />
  );
}



@Renderer({
  test: /(^|\/)(?:map|mapping)$/,
  name: 'mapping'
})
export class MappingFieldRenderer extends React.Component<RendererProps> {
  render() {
    return <MappingField {...this.props} />;
  }
}
