/**
 * Avatar ported from chakra-ui (license MIT)
 * Accessed 2021-12-26, component September 9th, 2021, theme August 23rd, 2021
 * - https://github.com/chakra-ui/chakra-ui/blob/5b3373d/packages/theme/src/components/avatar.ts
 * - https://github.com/chakra-ui/chakra-ui/blob/93c99bd/packages/avatar/src/avatar.tsx
 * Changes:
 * - Use .css instead of chakra styling props
 */
import React from "react";

import { useImage } from "../hooks/useImage";
import type { ImageProps } from "./Image";

import "../styles/ChakraAvatar.css";

interface AvatarOptions {
  /**
   * The name of the person in the avatar.
   *
   * - if `src` has loaded, the name will be used as the `alt` attribute of the `img`
   * - If `src` is not loaded, the name will be used to create the initials
   */
  name?: string;
  /**
   * If `true`, the `Avatar` will show a border around it.
   *
   * Best for a group of avatars
   */
  showBorder?: boolean;
  /**
   * The badge at the bottom right corner of the avatar.
   */
  children?: React.ReactNode;
  /**
   * The image url of the `Avatar`
   */
  src?: string;
  /**
   * List of sources to use for different screen resolutions
   */
  srcSet?: string;
  /**
   * Defines loading strategy
   */
  loading?: "eager" | "lazy";
  /**
   * The border color of the avatar
   * @type SystemProps["borderColor"]
   */
  borderColor?: React.CSSProperties["borderColor"];
  /**
   * Function called when image failed to load
   */
  onError?: () => void;
  /**
   * The default avatar used as fallback when `name`, and `src`
   * is not specified.
   * @type React.ReactElement
   */
  icon?: React.ReactElement;
  /**
   * Function to get the initials to display
   */
  getInitials?: (name: string) => string;
}

type AvatarBadgeProps = React.HTMLProps<HTMLDivElement>;

/**
 * AvatarBadge used to show extra badge to the top-right
 * or bottom-right corner of an avatar.
 */
export const AvatarBadge = React.forwardRef<HTMLDivElement, AvatarBadgeProps>(
  (props, ref) => {
    return (
      <div
        ref={ref}
        {...props}
        className={`chakra-avatar__badge ${props.className}`}
      />
    );
  },
);

AvatarBadge.displayName = "AvatarBadge";

function initials(name: string) {
  const [firstName, lastName] = name.split(" ");
  return firstName && lastName
    ? `${firstName.charAt(0)}${lastName.charAt(0)}`
    : firstName.charAt(0);
}

interface AvatarNameProps
  extends React.HTMLProps<HTMLDivElement>,
    Pick<AvatarOptions, "name" | "getInitials"> {}

/**
 * The avatar name container
 */
const AvatarName: React.FC<AvatarNameProps> = (props) => {
  const { name, getInitials, ...rest } = props;
  return (
    <div role="img" aria-label={name} {...rest} className="avatar__label">
      {name ? getInitials?.(name) : null}
    </div>
  );
};

/**
 * Fallback avatar react component.
 * This should be a generic svg used to represent an avatar
 */
const DefaultIcon = (props: React.HTMLProps<SVGElement>) => (
  // @ts-ignore
  <svg
    viewBox="0 0 128 128"
    color="#fff"
    width="100%"
    height="100%"
    className="avatar__svg"
    {...props}
  >
    <path
      fill="currentColor"
      d="M103,102.1388 C93.094,111.92 79.3504,118 64.1638,118 C48.8056,118 34.9294,111.768 25,101.7892 L25,95.2 C25,86.8096 31.981,80 40.6,80 L87.4,80 C96.019,80 103,86.8096 103,95.2 L103,102.1388 Z"
    />
    <path
      fill="currentColor"
      d="M63.9961647,24 C51.2938136,24 41,34.2938136 41,46.9961647 C41,59.7061864 51.2938136,70 63.9961647,70 C76.6985159,70 87,59.7061864 87,46.9961647 C87,34.2938136 76.6985159,24 63.9961647,24"
    />
  </svg>
);

export interface AvatarProps
  extends Omit<React.HTMLProps<HTMLSpanElement>, "onError">,
    AvatarOptions {
  iconLabel?: string;
  /**
   * If `true`, opt out of the avatar's `fallback` logic and
   * renders the `img` at all times.
   */
  ignoreFallback?: boolean;

  borderRadius?: React.CSSProperties["borderRadius"];
}

/**
 * Avatar component that renders an user avatar with
 * support for fallback avatar and name-only avatars
 */
export const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
  (props, ref) => {
    const {
      src,
      name,
      showBorder,
      borderRadius = "9999px",
      onError,
      getInitials = initials,
      icon = <DefaultIcon />,
      iconLabel = " avatar",
      loading,
      children,
      borderColor,
      ignoreFallback,
      style,
      ...rest
    } = props;

    const avatarStyles: React.CSSProperties = {
      borderRadius,
      borderWidth: showBorder ? "2px" : undefined,
      ...style,
    };

    if (borderColor) {
      avatarStyles.borderColor = borderColor as unknown as string;
    }

    return (
      <span
        ref={ref}
        {...rest}
        className={`avatar ${props.className ?? ""}`}
        style={avatarStyles}
      >
        <AvatarImage
          src={src}
          loading={loading}
          onError={onError}
          getInitials={getInitials}
          name={name}
          borderRadius={borderRadius}
          icon={icon}
          iconLabel={iconLabel}
          ignoreFallback={ignoreFallback}
        />
        {children}
      </span>
    );
  },
);

Avatar.displayName = "Avatar";

interface AvatarImageProps
  extends ImageProps,
    Pick<AvatarProps, "getInitials" | "borderRadius" | "icon" | "name"> {
  iconLabel?: string;
}

const AvatarImage: React.FC<AvatarImageProps> = (props) => {
  const {
    src,
    onError,
    getInitials,
    name,
    borderRadius,
    loading,
    iconLabel,
    icon = <DefaultIcon />,
    ignoreFallback,
  } = props;

  /**
   * use the image hook to only show the image when it has loaded
   */
  const status = useImage({ src, onError, ignoreFallback });
  const hasLoaded = status === "loaded";

  /**
   * Fallback avatar applies under 2 conditions:
   * - If `src` was passed and the image has not loaded or failed to load
   * - If `src` wasn't passed
   *
   * In this case, we'll show either the name avatar or default avatar
   */
  const showFallback = !src || !hasLoaded;

  if (showFallback) {
    return name ? (
      <AvatarName
        className="avatar__initials"
        getInitials={getInitials}
        name={name}
      />
    ) : (
      React.cloneElement(icon, {
        role: "img",
        "aria-label": iconLabel,
      })
    );
  }

  /**
   * If `src` was passed and the image has loaded, we'll show it
   */
  return (
    <img
      src={src}
      alt={name}
      className="chakra-avatar__img"
      loading={loading}
      style={{
        width: "100%",
        height: "100%",
        objectFit: "cover",
        ...(borderRadius ? { borderRadius } : {}),
      }}
    />
  );
};

AvatarImage.displayName = "AvatarImage";
