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

import Icon, { IconName } from '../Icon';
import Button from '../Button';
import {
  StyledAlert,
  StyledInner,
  StyledTitle,
  StyledContent,
  StyledIconWrapper,
  StyledCloseButton,
} from './StyledAlert';
import { ICON_MAP, ICON_SIZE_MAP } from './constants';
import { CommonProps } from '../common';
import { getOrElse, map, none, Option, some } from '../../fp/Option';
import { Either, left, match, right } from '../../fp/Either';
import { pipe } from '../../fp/function';

type AlertSize = 'default' | 'compact';

export interface AlertProps extends CommonProps {
  /**
   * Alert content.
   */
  content: string | ReactElement;
  /**
   * Icon name or a react element as custom icon.
   * - undefined: use default icon according to Alert 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 alert.
   */
  intent?: 'success' | 'info' | 'warning' | 'danger' | 'error';
  /**
   * Closing callback. When onClose is available, an `x` button will be rendered on the right side of alert. The callback will be called when user clicks on `x` button.
   */
  onClose?: () => void;
  /**
   * Alert title.
   */
  title?: string | ReactElement;
}

export const getAlertIcon = (
  defaultIcon: IconName,
  icon: undefined | null | IconName | ReactElement
): Option<Either<IconName, ReactElement>> => {
  if (icon === undefined) {
    return some(left(defaultIcon));
  }

  if (typeof icon === 'string' && Icon.List.includes(icon)) {
    return some(left(icon));
  }

  if (React.isValidElement(icon)) {
    return some(right(icon));
  }

  return none;
};

const Alert = ({
  intent = 'info',
  title,
  content,
  icon,
  onClose,
  id,
  className,
  style,
  sx = {},
  'data-test-id': dataTestId,
}: AlertProps): ReactElement => {
  const themeSize: AlertSize = title !== undefined ? 'default' : 'compact';

  const defaultIcon = ICON_MAP[intent];
  const maybeIcon = getAlertIcon(defaultIcon, icon);
  const iconIntent = intent;
  const iconSize = ICON_SIZE_MAP[themeSize];

  const renderWithIconWrapper = (iconEle: ReactElement): ReactElement => (
    <StyledIconWrapper themeSize={themeSize} data-test-id="alert-icon">
      {iconEle}
    </StyledIconWrapper>
  );

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

export default Alert;
