import { ForwardedRef, forwardRef } from 'react';

import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { assertUnreachable } from '../../utils/assertUnreachable';

import { BadgeBlock, BadgeBlockProps } from './components/BadgeBlock';
import { BadgeButton, BadgeButtonProps } from './components/BadgeButton';
import { BadgeLink, BadgeLinkProps } from './components/BadgeLink';
import { BadgeType } from './constants';

/** Props for {@link Badge} */
export type BadgeProps = {
  /**
   * Determines the component modification
   * Depending on this, the component can require additional props.
   */
  type?: BadgeType;
} & (
  | (BadgeBlockProps & {
      type?: BadgeType.Default;
    })
  | (BadgeButtonProps & {
      type: BadgeType.Button;
    })
  | (BadgeLinkProps & {
      type: BadgeType.Link;
    })
);

/**
 * A Badge is a visual indicator for an item's status or numeric values.
 *
 * ### Interactive
 *
 * Badges can be represented as `<button>` or as `<a>` elements.
 *
 * #### Buttons
 *
 * Badges can be clickable.
 *
 * <Story id="components-badge--as-button" />
 *
 * ```tsx
 * import { Badge, BadgeType } from 'ui-kit'
 *
 * <Badge type={BadgeType.Button} onClick={handleBadgeClick}>List</Badge>
 * ```
 *
 * #### Links
 *
 * Badges can be represented as link.
 *
 * <Story id="components-badge--as-link" />
 *
 * ```tsx
 * import { Badge, BadgeType } from 'ui-kit'
 *
 * <Badge type={BadgeType.Link} to="/dashboard">Dashboard</Badge>
 * ```
 *
 * ### Colors
 *
 * If the color is not specified the Badge has a gray background.
 *
 * <Story id="components-badge--default" />
 *
 * Optionally you can set a specific style bypassing the `color` prop with one of the following styles:
 * `BadgeColor.Default`, `BadgeColor.Blue`, `BadgeColor.Green`, `BadgeColor.Orange`, `BadgeColor.Red`.
 *
 * <Story id="components-badge--badges" />
 *
 * ### Variant
 *
 * Sometimes using the default colors is not enough. For example, the badges may be less noticeable on gray
 * backgrounds. In this case, use the `variant` prop to modify the Badge. You may apply it to any color,
 * including the default one.
 *
 * <Story id="components-badge--bold-badges" />
 *
 * ### Patterns
 *
 * We have a number of common patterns for Badge's colors usage.
 *
 * | Green                                                      | Orange                                                     | Red                                                        | Blue                                                       |
 * | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
 * | <Story id="components-badge-patterns--success" noCanvas /> | <Story id="components-badge-patterns--new" noCanvas />     | <Story id="components-badge-patterns--blocked" noCanvas /> | <Story id="components-badge-patterns--pending" noCanvas /> |
 * | <Story id="components-badge-patterns--done" noCanvas />    | <Story id="components-badge-patterns--warning" noCanvas /> | <Story id="components-badge-patterns--expired" noCanvas /> |                                                            |
 * | <Story id="components-badge-patterns--active" noCanvas />  |                                                            |                                                            |                                                            |
 *
 * ### Badges with icons
 *
 * You can add an icon to badge by set `icon` prop.
 *
 * <Story id="components-badge--icon-badges" />
 */
export const Badge = forwardRef<HTMLElement, BadgeProps>((props, ref) => {
  const { type = BadgeType.Default, ...restProps } = props;

  switch (type) {
    case BadgeType.Link: {
      const { to, icon, className, children, color, variant, ariaLabel, testId, ariaDescribedBy, ...rest } =
        restProps as BadgeLinkProps;
      assertEmptyObject(rest);
      return (
        <BadgeLink
          ref={ref as ForwardedRef<HTMLAnchorElement>}
          ariaDescribedBy={ariaDescribedBy}
          ariaLabel={ariaLabel}
          className={className}
          color={color}
          icon={icon}
          testId={testId}
          to={to}
          variant={variant}
        >
          {children}
        </BadgeLink>
      );
    }
    case BadgeType.Button: {
      const {
        onClick,
        variant,
        ariaLabel,
        className,
        children,
        color,
        icon,
        testId,
        ariaDescribedBy,
        ...rest
      } = restProps as BadgeButtonProps;
      assertEmptyObject(rest);
      return (
        <BadgeButton
          ref={ref as ForwardedRef<HTMLButtonElement>}
          ariaDescribedBy={ariaDescribedBy}
          ariaLabel={ariaLabel}
          className={className}
          color={color}
          icon={icon}
          onClick={onClick}
          testId={testId}
          variant={variant}
        >
          {children}
        </BadgeButton>
      );
    }
    case BadgeType.Default:
      const { icon, className, color, variant, ariaLabel, children, testId, ariaDescribedBy, ...rest } =
        restProps as BadgeBlockProps;
      assertEmptyObject(rest);
      return (
        <BadgeBlock
          ref={ref as ForwardedRef<HTMLDivElement>}
          ariaDescribedBy={ariaDescribedBy}
          ariaLabel={ariaLabel}
          className={className}
          color={color}
          icon={icon}
          testId={testId}
          variant={variant}
        >
          {children}
        </BadgeBlock>
      );
    /* istanbul ignore next */
    default:
      assertUnreachable(type);
  }
});
