import type { VueNode } from '../_util/type';
import { tuple } from '../_util/type';
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import { computed, defineComponent, nextTick, onMounted, ref, watch } from 'vue';
import { getPropsSlot } from '../_util/props-util';
import PropTypes from '../_util/vue-types';
import useBreakpoint from '../_util/hooks/useBreakpoint';
import type { Breakpoint, ScreenSizeMap } from '../_util/responsiveObserve';
import { responsiveArray } from '../_util/responsiveObserve';
import useConfigInject from '../_util/hooks/useConfigInject';
import ResizeObserver from '../vc-resize-observer';
import { useInjectSize } from '../_util/hooks/useSize';
import eagerComputed from '../_util/eagerComputed';

export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;

export const avatarProps = () => ({
  prefixCls: PropTypes.string,
  shape: PropTypes.oneOf(tuple('circle', 'square')).def('circle'),
  size: {
    type: [Number, String, Object] as PropType<AvatarSize>,
    default: (): AvatarSize => 'default',
  },
  src: PropTypes.string,
  /** Srcset of image avatar */
  srcset: PropTypes.string,
  icon: PropTypes.any,
  alt: PropTypes.string,
  gap: PropTypes.number,
  draggable: PropTypes.bool,
  crossOrigin: String as PropType<'' | 'anonymous' | 'use-credentials'>,
  loadError: {
    type: Function as PropType<() => boolean>,
  },
});

export type AvatarProps = Partial<ExtractPropTypes<ReturnType<typeof avatarProps>>>;

const Avatar = defineComponent({
  name: 'AAvatar',
  inheritAttrs: false,
  props: avatarProps(),
  slots: ['icon'],
  setup(props, { slots, attrs }) {
    const isImgExist = ref(true);
    const isMounted = ref(false);
    const scale = ref(1);

    const avatarChildrenRef = ref<HTMLElement>(null);
    const avatarNodeRef = ref<HTMLElement>(null);

    const { prefixCls } = useConfigInject('avatar', props);

    const groupSize = useInjectSize();
    const size = computed(() => {
      return props.size === 'default' ? groupSize.value : props.size;
    });
    const screens = useBreakpoint();
    const responsiveSize = eagerComputed(() => {
      if (typeof props.size !== 'object') {
        return undefined;
      }
      const currentBreakpoint: Breakpoint = responsiveArray.find(screen => screens.value[screen])!;
      const currentSize = props.size[currentBreakpoint];

      return currentSize;
    });

    const responsiveSizeStyle = (hasIcon: boolean) => {
      if (responsiveSize.value) {
        return {
          width: `${responsiveSize.value}px`,
          height: `${responsiveSize.value}px`,
          lineHeight: `${responsiveSize.value}px`,
          fontSize: `${hasIcon ? responsiveSize.value / 2 : 18}px`,
        };
      }
      return {};
    };

    const setScaleParam = () => {
      if (!avatarChildrenRef.value || !avatarNodeRef.value) {
        return;
      }
      const childrenWidth = avatarChildrenRef.value.offsetWidth; // offsetWidth avoid affecting be transform scale
      const nodeWidth = avatarNodeRef.value.offsetWidth;
      // denominator is 0 is no meaning
      if (childrenWidth !== 0 && nodeWidth !== 0) {
        const { gap = 4 } = props;
        if (gap * 2 < nodeWidth) {
          scale.value =
            nodeWidth - gap * 2 < childrenWidth ? (nodeWidth - gap * 2) / childrenWidth : 1;
        }
      }
    };

    const handleImgLoadError = () => {
      const { loadError } = props;
      const errorFlag = loadError?.();
      if (errorFlag !== false) {
        isImgExist.value = false;
      }
    };

    watch(
      () => props.src,
      () => {
        nextTick(() => {
          isImgExist.value = true;
          scale.value = 1;
        });
      },
    );

    watch(
      () => props.gap,
      () => {
        nextTick(() => {
          setScaleParam();
        });
      },
    );

    onMounted(() => {
      nextTick(() => {
        setScaleParam();
        isMounted.value = true;
      });
    });

    return () => {
      const { shape, src, alt, srcset, draggable, crossOrigin } = props;
      const icon = getPropsSlot(slots, props, 'icon');
      const pre = prefixCls.value;
      const classString = {
        [`${attrs.class}`]: !!attrs.class,
        [pre]: true,
        [`${pre}-lg`]: size.value === 'large',
        [`${pre}-sm`]: size.value === 'small',
        [`${pre}-${shape}`]: shape,
        [`${pre}-image`]: src && isImgExist.value,
        [`${pre}-icon`]: icon,
      };

      const sizeStyle: CSSProperties =
        typeof size.value === 'number'
          ? {
              width: `${size.value}px`,
              height: `${size.value}px`,
              lineHeight: `${size.value}px`,
              fontSize: icon ? `${size.value / 2}px` : '18px',
            }
          : {};

      const children: VueNode = slots.default?.();
      let childrenToRender;
      if (src && isImgExist.value) {
        childrenToRender = (
          <img
            draggable={draggable}
            src={src}
            srcset={srcset}
            onError={handleImgLoadError}
            alt={alt}
            crossorigin={crossOrigin}
          />
        );
      } else if (icon) {
        childrenToRender = icon;
      } else if (isMounted.value || scale.value !== 1) {
        const transformString = `scale(${scale.value}) translateX(-50%)`;
        const childrenStyle: CSSProperties = {
          msTransform: transformString,
          WebkitTransform: transformString,
          transform: transformString,
        };
        const sizeChildrenStyle =
          typeof size.value === 'number'
            ? {
                lineHeight: `${size.value}px`,
              }
            : {};
        childrenToRender = (
          <ResizeObserver onResize={setScaleParam}>
            <span
              class={`${pre}-string`}
              ref={avatarChildrenRef}
              style={{ ...sizeChildrenStyle, ...childrenStyle }}
            >
              {children}
            </span>
          </ResizeObserver>
        );
      } else {
        childrenToRender = (
          <span class={`${pre}-string`} ref={avatarChildrenRef} style={{ opacity: 0 }}>
            {children}
          </span>
        );
      }
      return (
        <span
          {...attrs}
          ref={avatarNodeRef}
          class={classString}
          style={{
            ...sizeStyle,
            ...responsiveSizeStyle(!!icon),
            ...(attrs.style as CSSProperties),
          }}
        >
          {childrenToRender}
        </span>
      );
    };
  },
});

export default Avatar;
