import React, { useContext, useMemo, useState } from "react";
import md5 from "md5";
import classNames from "classnames";

import { bemHOF } from "../../utilities/bem";
import { colors } from "../../tokens";
import { Box, BoxProps } from "../Box";
import { Icon, ICON_TYPE } from "../Icon";
import { Flex } from "../Flex";
import { Text } from "../Text";
import { Image } from "../Image";
import { Tooltip } from "../Tooltip";
import { Svg, SvgProps } from "../Svg";

import { AvatarGroup } from "./AvatarGroup";
import { AvatarSizesForGravatar, AvatarSize, AVATAR_TYPE } from "./types";
import { AvatarContext } from "./context";

const cn = bemHOF("Avatar");

const SystemIcon = (props: SvgProps) => {
  return (
    <Svg viewBox="0 0 32 32" {...props}>
      <path
        fill={colors.brandGreen["400"]}
        d="M19.042 31.58c-1.17 0.275-2.389 0.42-3.642 0.42-0.24 0-0.479-0.005-0.716-0.016v-2.851c0-1.318-1.285-2.25-2.533-1.843-1.934 0.63-3.873 1.249-5.809 1.872-1.371-0.954-2.586-2.118-3.598-3.447l7.94-2.602c1.96-0.637 4.054-0.299 5.721 0.918s2.633 3.115 2.637 5.186l0.001 2.362z"
      />
      <path
        fill={colors.brandGreen["400"]}
        d="M20.856 31.037l0.004-8.342c-0.003-2.071 0.95-3.969 2.636-5.191 1.647-1.216 3.736-1.548 5.716-0.909l2.066 0.675c-0.117 1.502-0.441 2.946-0.943 4.302-0.876-0.286-1.751-0.576-2.627-0.861-1.23-0.401-2.48 0.525-2.48 1.824v6.057c-1.308 1.032-2.781 1.862-4.372 2.445z"
      />
      <path
        fill={colors.brandGreen["400"]}
        d="M31.314 15.338l-7.709-2.523c-1.98-0.64-3.471-2.149-4.105-4.12s-0.299-4.075 0.911-5.75l1.212-1.677c1.363 0.582 2.629 1.349 3.767 2.27-0.512 0.704-1.032 1.403-1.555 2.099-0.802 1.066-0.321 2.608 0.946 3.015l5.558 1.785c0.567 1.536 0.906 3.184 0.976 4.901z"
      />
      <path
        fill={colors.brandGreen["400"]}
        d="M14.89 0.008c0.169-0.005 0.339-0.008 0.509-0.008 1.534 0 3.017 0.218 4.421 0.624l-4.725 6.537c-1.211 1.675-3.098 2.645-5.159 2.648s-3.949-0.956-5.166-2.651l-1.287-1.776c0.971-1.099 2.091-2.063 3.327-2.859l1.565 2.223c0.764 1.085 2.365 1.090 3.135 0.008l3.379-4.747z"
      />
      <path
        fill={colors.brandGreen["400"]}
        d="M0.145 11.383c0.487-1.624 1.223-3.14 2.165-4.502l4.823 6.663c1.217 1.695 1.548 3.795 0.906 5.745-0.636 1.99-2.137 3.487-4.097 4.124l-2.253 0.733c-0.737-1.249-1.31-2.608-1.689-4.046l2.681-0.912c1.244-0.423 1.711-1.948 0.93-3.009-1.168-1.589-2.318-3.193-3.466-4.797z"
      />
    </Svg>
  );
};

// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemIconConveyor = (props: SvgProps) => (
  <SystemIcon {...props} />
);

// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemIconAptible = (props: SvgProps) => (
  <Svg viewBox="0 0 40 32" {...props}>
    <path
      fill={colors.gold["400"]}
      d="M22.018 31.667h5.646v-11.538l11.291 11.538v-7.692l-16.937-17.308z"
    />
    <path
      fill={colors.gold["400"]}
      d="M27.777 0h-7.452l18.631 18.333v-7.333l-11.178-11z"
    />
    <path
      fill={colors.gold["400"]}
      d="M0 23.974v7.692l11.291-11.538v11.538h5.646v-25l-16.937 17.308z"
    />
    <path
      fill={colors.gold["400"]}
      d="M0 11v7.333l18.631-18.333h-7.522l-11.109 11z"
    />
  </Svg>
);

export interface SharedAvatarProps extends BoxProps {
  /**
   * The size to render the avatar.
   */
  size?: AvatarSize;
  /**
   * Makes the avatar a square with rounded corners and a box shadow; good to
   * use when overlapping avatars.
   */
  square?: boolean;
  /**
   * Adds a border around the avatar.
   */
  bordered?: boolean;
  /**
   * Alternate text for accessibility; most times it’s better to leave this
   * null.
   */
  alt?: string;
}

type AnonymousAvatarProps = SharedAvatarProps & {
  type: AVATAR_TYPE.ANONYMOUS;
};

type SystemAvatarProps = SharedAvatarProps & {
  type: AVATAR_TYPE.SYSTEM;
};

interface UserAvatarComponentProps extends GravatarOrInitialsProps {
  /**
   * Path to an image.
   */
  imgSrc?: string;
  /**
   * Whether or not to use a tooltip to display the user’s name; it
   * only applies when `type=user`. If `name` is not provided, it’ll
   * use `email` if it exists. The tooltip won’t show if either is
   * missing.
   */
  enableTooltip?: boolean;
}

type UserAvatarProps = SharedAvatarProps &
  UserAvatarComponentProps & {
    type: AVATAR_TYPE.USER;
  };

export type AvatarProps =
  | AnonymousAvatarProps
  | SystemAvatarProps
  | UserAvatarProps;

const avatarInitialBackgroundColors: string[] = [
  colors.green["600"],
  colors.gold["600"],
  colors.orange["600"],
  colors.red["600"],
  colors.pink["600"],
  colors.purple["600"],
  colors.blue["600"],
  colors.cyan["600"],
];

interface InitialsProps {
  /**
   * Email address for the user; this is used to look up a Gravatar
   * image or as a key to provide a unique color when the initials fallback
   * is used.
   */
  email: string;
  /**
   * The user’s name; this will be used to automatically create the
   * user’s initials. It’ll also display in a tooltip when
   * `enableTooltip=true`.
   */
  name?: string;
  /**
   * The user’s first name initial; this can be omitted if a `name` is passed.
   */
  firstInitial?: string;
  /**
   * The user’s last name initial; this can be omitted if a `name` is passed.
   */
  lastInitial?: string;
}

interface GravatarOrInitialsProps extends InitialsProps {
  size?: AvatarSize;
  alt?: string;
}

const Initials = ({
  email,
  name,
  firstInitial,
  lastInitial,
}: InitialsProps) => {
  const initialsBgColor: string = useMemo(() => {
    const fallbackColor = colors.gray["600"];
    if (!email) return fallbackColor;

    let hash = 0;
    for (let i = 0; i < email.length; i += 1) {
      // eslint-disable-next-line no-bitwise
      hash = email.charCodeAt(i) + ((hash << 5) - hash);
    }
    const colorIndex = Math.abs(hash % avatarInitialBackgroundColors.length);

    return avatarInitialBackgroundColors[colorIndex] || fallbackColor;
  }, [email]);

  // Generate the initials from the name
  const initialsFromName: string = name
    ? name
        .split(" ") // Separate first and last names
        .map((n: string) => n.charAt(0)) // Get the first letter of each name
        .join("") // Join them back together
        .slice(0, 2) // Take the first two initials (in case of multiple word surnames)
    : "";

  // Use the first letter of the email address instead of initials if not provided
  const initials =
    firstInitial === undefined && lastInitial === undefined
      ? email.charAt(0)
      : `${firstInitial}${lastInitial}`;

  return (
    <Flex
      className={cn({ e: "initials-wrapper" })}
      style={{ backgroundColor: initialsBgColor }}
    >
      <Text as="span" className={cn({ e: "initials" })}>
        {initialsFromName || initials}
      </Text>
    </Flex>
  );
};

const GravatarOrInitials = ({
  email,
  size = "medium",
  name,
  firstInitial,
  lastInitial,
  alt,
}: GravatarOrInitialsProps) => {
  const [hasImageError, setImageError] = useState(false);
  const src = useMemo(() => {
    const md5Email = email
      ? md5(email.trim().toLowerCase(), { encoding: "binary" })
      : null;
    return `https://www.gravatar.com/avatar/${md5Email}?d=404&r=pg&s=${AvatarSizesForGravatar[size]}`;
  }, [email, size]);

  if (hasImageError) {
    return (
      <Initials
        email={email}
        name={name}
        firstInitial={firstInitial}
        lastInitial={lastInitial}
      />
    );
  }

  return (
    <Image
      alt={alt}
      src={src}
      className={cn({ e: "img" })}
      onError={() => setImageError(true)}
      onLoad={() => setImageError(false)}
    />
  );
};

const UserAvatar = ({
  imgSrc,
  email = "",
  alt,
  ...rest
}: UserAvatarComponentProps) => {
  // If an imgSrc is provided, just use that as the avatar
  if (imgSrc) {
    return <Image alt={alt} src={imgSrc} className={cn({ e: "img" })} />;
  }

  // If no imgSrc, then use the Gravatar
  return <GravatarOrInitials email={email} alt={alt} {...rest} />;
};

const AnonymousAvatar = () => (
  <Box className={cn({ e: "icon", m: "anonymous" })}>
    <Icon icon={ICON_TYPE.USER_ASTRONAUT} />
  </Box>
);

const SystemAvatar = () => {
  return (
    <Box className={cn({ e: "icon", m: "system" })}>
      <SystemIcon
        aria-hidden="true"
        className="svg-inline--fa fa-w-8"
        role="img"
      />
    </Box>
  );
};

// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemAvatarConveyor = () => {
  return <SystemAvatar />;
};

// Deprecated; Remove after dual-branding in Comply is no longer needed
// Exported to create a dual-branded system avatar in Comply
export const SystemAvatarAptible = () => {
  return (
    <Box className={cn({ e: "icon", m: "system" })}>
      <SystemIconAptible
        aria-hidden="true"
        className="svg-inline--fa fa-w-10"
        role="img"
      />
    </Box>
  );
};

const NonUserAvatarComponent = (type: AVATAR_TYPE) => {
  return {
    anonymous: <AnonymousAvatar />,
    user: <AnonymousAvatar />,
    system: <SystemAvatar />,
  }[type];
};

export const AvatarWrapper = ({
  children,
  size,
  square,
  bordered,
  className,
  ...rest
}: SharedAvatarProps) => (
  <Box
    className={classNames(
      cn(),
      cn({ m: size }),
      square && cn({ m: "square" }),
      bordered && cn({ m: "bordered" }),
      className,
    )}
    {...rest}
  >
    {children}
  </Box>
);

export const Avatar = ({
  size: sizeProp = "medium",
  square,
  bordered,
  className,
  style,
  alt,
  ...rest
}: AvatarProps) => {
  const avatarContext = useContext(AvatarContext);
  const size = avatarContext ? avatarContext.size : sizeProp;

  if (rest.type === AVATAR_TYPE.USER && (rest.email || rest.imgSrc)) {
    const {
      type,
      email,
      imgSrc,
      name,
      firstInitial,
      lastInitial,
      enableTooltip = false,
      ...userRest
    } = rest;
    return (
      <AvatarWrapper
        size={size}
        square={square}
        bordered={bordered}
        className={className}
        style={style}
        title={alt}
        {...userRest}
      >
        {enableTooltip && (name || email) ? (
          <Tooltip
            placement="top"
            referenceElement={
              <Box className="h-full">
                <UserAvatar
                  email={email}
                  imgSrc={imgSrc}
                  name={name}
                  firstInitial={firstInitial}
                  lastInitial={lastInitial}
                  size={size}
                  alt={alt}
                />
              </Box>
            }
          >
            {name || email}
          </Tooltip>
        ) : (
          <UserAvatar
            email={email}
            imgSrc={imgSrc}
            name={name}
            firstInitial={firstInitial}
            lastInitial={lastInitial}
            size={size}
            alt={alt}
          />
        )}
      </AvatarWrapper>
    );
  }

  const { type, ...nonUserRest } = rest;

  if (type === AVATAR_TYPE.USER) {
    if ("name" in nonUserRest) {
      delete nonUserRest.name;
    }
    if ("firstInitial" in nonUserRest) {
      delete nonUserRest.firstInitial;
    }
    if ("lastInitial" in nonUserRest) {
      delete nonUserRest.lastInitial;
    }
  }

  return (
    <AvatarWrapper
      size={size}
      square={square}
      bordered={bordered}
      className={className}
      style={style}
      title={alt}
      {...nonUserRest}
    >
      {NonUserAvatarComponent(type)}
    </AvatarWrapper>
  );
};

Avatar.Group = AvatarGroup;
