import React, { ReactElement } from 'react';
import css from '../../utils/css';

import Icon, { IconName } from '../Icon';
import Button from '../Button';
import StyledNotification, {
  StyledInner,
  StyledTitle,
  StyledContent,
  StyledIconWrapper,
  StyledCloseButton,
} from './StyledNotification';

import { CommonProps } from '../common';
import { Either, left, match, right } from '../../fp/Either';
import { getOrElse, map, none, Option, some } from '../../fp/Option';
import { pipe } from '../../fp/function';

type NotificationIntent = 'success' | 'info' | 'warning' | 'danger' | 'error';

export interface NotificationProps extends CommonProps {
  /**
   * Notification content.
   */
  content?: string | ReactElement;
  /**
   * Icon name or a react element as custom icon.
   * - undefined: use default icon according to Notification intent.
   * - null: no icon at all.
   * - IconName: an icon identifier from hero-design icon list.
   * - ReactElement: Custom icon by your own.
   */
  icon?: null | IconName | ReactElement;
  /**
   * Visual intent color to apply to notification icon.
   */
  intent?: NotificationIntent;
  /**
   * Closing callback. When onClose is available, an `x` button will be rendered on the right side of notification. The callback will be called when user clicks on `x` button.
   */
  onClose?: () => void;
  /**
   * Notification title.
   */
  title: string | ReactElement;
}

const getIcon = (
  intent: NotificationIntent
): {
  icon: IconName;
  intent: NotificationIntent;
} => {
  switch (intent) {
    case 'success':
      return { icon: 'circle-ok', intent: 'success' };
    case 'info':
      return { icon: 'circle-info', intent: 'info' };
    case 'warning':
      return { icon: 'circle-warning', intent: 'warning' };
    case 'danger':
      return { icon: 'circle-cancel', intent: 'danger' };
    case 'error':
      return { icon: 'circle-cancel', intent: 'error' };
    default:
      return { icon: 'circle-ok', intent: 'success' };
  }
};

const getNotificationIcon = (
  defaultIcon: IconName,
  icon: undefined | null | IconName | ReactElement
): Option<Either<IconName, ReactElement>> => {
  if (icon === undefined) {
    return some(left(defaultIcon));
  }
  // FIXME: Logic on type guard is not expected
  if (typeof icon === 'string') {
    return some(left(icon));
  }
  if (React.isValidElement(icon)) {
    return some(right(icon));
  }
  return none;
};

const Notification = ({
  intent = 'info',
  title,
  content,
  icon,
  onClose,
  id,
  className,
  style,
  sx = {},
  'data-test-id': dataTestId,
}: NotificationProps): ReactElement => {
  const { icon: defaultIcon, intent: iconIntent } = getIcon(intent);
  const maybeIcon = getNotificationIcon(defaultIcon, icon);

  const renderWithIconWrapper = (iconEle: ReactElement): ReactElement => (
    <StyledIconWrapper>{iconEle}</StyledIconWrapper>
  );

  return (
    <StyledNotification
      id={id}
      className={className}
      style={{ ...style, ...css(sx) }}
      data-test-id={dataTestId}
    >
      {pipe(
        maybeIcon,
        map(
          match(
            (iconString): ReactElement =>
              renderWithIconWrapper(
                <Icon
                  icon={iconString}
                  intent={iconIntent}
                  size="xlarge"
                  data-test-id="notification-icon"
                />
              ),
            renderWithIconWrapper
          )
        ),
        getOrElse(() => null)
      )}
      <StyledInner>
        <StyledTitle>{title}</StyledTitle>
        <StyledContent>{content}</StyledContent>
      </StyledInner>
      {onClose !== undefined && (
        <StyledCloseButton>
          <Button.Icon
            icon="cancel"
            intent="subdued-text"
            onClick={onClose}
            data-test-id="close-notification-button"
          />
        </StyledCloseButton>
      )}
    </StyledNotification>
  );
};

export default Notification;
