import { Check } from '@transferwise/icons';
import { clsx } from 'clsx';
import React, { forwardRef, FunctionComponent, useEffect, useId, useState } from 'react';

import Body from '../body';
import { Typography } from '../common';
import BaseCard, { type BaseCardProps } from '../common/baseCard';
import Display from '../display';
import Image from '../image/Image';
import Title from '../title';

import { usePromoCardContext } from './PromoCardContext';
import PromoCardIndicator, { type PromoCardIndicatorProps } from './PromoCardIndicator';

export type ReferenceType = React.Ref<HTMLInputElement> | React.Ref<HTMLDivElement>;
export type RelatedTypes =
  | ''
  | 'alternate'
  | 'author'
  | 'bookmark'
  | 'external'
  | 'help'
  | 'license'
  | 'next'
  | 'nofollow'
  | 'noreferrer'
  | 'noopener'
  | 'prev'
  | 'search'
  | 'tag';

export interface PromoCardCommonProps {
  /** Optional prop to specify classNames onto the PromoCard */
  className?: string;

  /** Optional prop to specify the ID of the PromoCard */
  id?: string;

  /** Required prop to specify the descriptive text of the PromoCard  */
  description: string;

  /**
   * Optional prop to specify the heading level of the PromoCard
   *
   * @default 'h3'
   */
  headingLevel?: 'h3' | 'h4' | 'h5' | 'h6';

  /** Optional prop to specify text for the indicator label of the PromoCard */
  indicatorLabel?: string;

  /** Optional prop to specify the icon for the indicator icon of the PromoCard */
  indicatorIcon?: PromoCardIndicatorProps['icon'];

  /** Optional prop to specify an image alt text  */
  imageAlt?: string;

  /** Optional prop to specify an image class  */
  imageClass?: string;

  /** Optional prop to specify an image source url  */
  imageSource?: string;

  /** Specify whether the PromoCard is disabled, or not */
  isDisabled?: boolean;

  /** Specify an onClick event handler */
  onClick?: () => void;

  /** Specify an onKeyDown event handler */
  onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;

  /** Optional prop to specify the ID used for testing */
  testId?: string;

  /** Required prop to specify the title text of the PromoCard  */
  title: string;

  /** Set to false to use body font style for the title   */
  useDisplayFont?: boolean;

  ref?: ReferenceType;
}

export interface PromoCardLinkProps extends PromoCardCommonProps, Omit<BaseCardProps, 'children'> {
  /**
   * Optional prop to prompts a user to save the linked URL instead of
   * navigating to it
   */
  download?: string;

  /** Optionally specify an href for your PromoCard to contain an <a> element */
  href?: string;

  /** Optionally specify the language of the linked URL */
  hrefLang?: string;

  /** Optional property that can be pass a ref for the anchor. */
  anchorRef?: React.Ref<HTMLAnchorElement>;

  /**
   * Optional prop to specify the ID of the anchor element which can be useful when using a ref.
   */
  anchorId?: string;

  /**
   * Relationship between the PromoCard href URL and the current page. See
   * [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
   */
  rel?: RelatedTypes;

  /** Optional prop to to display where the linked URL will show */
  target?: React.HTMLAttributeAnchorTarget;

  /** Only applies to role="radio" or "checkbox" */
  defaultChecked?: never;
  isChecked?: never;
  tabIndex?: never;
  type?: never;
  ref?: ReferenceType;
  value?: never;
}

export interface PromoCardCheckedProps
  extends PromoCardCommonProps, Omit<BaseCardProps, 'children'> {
  /** Specify the initial checked attribute of the PromoCard */
  defaultChecked?: boolean;

  /** Specify whether the PromoCard is checked, or not */
  isChecked?: boolean;

  /** Optional prop to specify the tabIndex of the PromoCard */
  tabIndex?: number;

  /** Optional property to provide component Ref */
  ref?: ReferenceType;

  /** Optional prop to specify the input type of the PromoCard  */
  type?: 'checkbox' | 'radio';

  /** Specify the value attribute of the PromoCard if Checkbox or Radio */
  value?: string;

  /** Only applies to <a />s */
  download?: never;
  href?: never;
  anchorRef?: never;
  anchorId?: never;
  hrefLang?: never;
  rel?: never;
  target?: never;
}

export type PromoCardProps = PromoCardLinkProps | PromoCardCheckedProps;

export type PolymorphicPromoCard = (
  props: PromoCardLinkProps | PromoCardCheckedProps,
) => React.JSX.Element;

/**
 * PromoCard component.
 *
 * PromoCard is a marketing style component that is used to group marketing
 * product related information (such as choosing Card types). It can be used to
 * display information in a structured way, and can be customized with various
 * props to suit different use cases.
 *
 * @component
 * @param {string} className - Additional class name for the PromoCard.
 * @param {string} description - Description text for the PromoCard.
 * @param {boolean} defaultChecked - Initial checked state for checkboxes and radios.
 * @param {string} download - Download file name for links.
 * @param {string} href - URL for links.
 * @param {string} hrefLang - Language code for linked URL.
 * @param {string} id - ID of the PromoCard.
 * @param {string} imageAlt - Alt text for the image.
 * @param {string} imageSource - Source URL of the image.
 * @param {string} indicatorLabel - Label for the indicator icon.
 * @param {boolean} isChecked - Checked state for checkboxes and radios.
 * @param {boolean} isDisabled - Whether the PromoCard is disabled.
 * @param {Function} onClick - Click event handler for the PromoCard.
 * @param {string} rel - Relationship between the URL and the current page.
 * @param {number} tabIndex - Tab index for keyboard navigation.
 * @param {string} target - Target window for links.
 * @param {string} testId - ID used for testing.
 * @param {string} title - Title text of the PromoCard.
 * @param {('checkbox'|'radio')} type - Type of the PromoCard (checkbox, radio).
 * @param {string} value - Value for checkboxes and radios.
 * @returns {React.JSX.Element} The rendered PromoCard component.
 * @example
 * <PromoCard
 *   title="Example PromoCard"
 *   description="This is an example PromoCard with a link variation."
 *   href="https://example.com"
 *   target="_blank"
 *   imageSource="https://example.com/image.png"
 *   imageAlt="Example Image"
 *   indicatorLabel="Learn More"
 * />
 */
const PromoCard: FunctionComponent<PromoCardProps> = forwardRef(
  (
    {
      className,
      description,
      defaultChecked,
      download,
      href,
      hrefLang,
      id,
      headingLevel = 'h3',
      imageAlt,
      imageClass,
      imageSource,
      indicatorLabel,
      indicatorIcon,
      isChecked,
      isDisabled,
      onClick,
      onKeyDown,
      rel,
      tabIndex,
      target,
      testId,
      title,
      type,
      value,
      isSmall,
      useDisplayFont = true,
      anchorRef,
      anchorId,
      ...props
    },
    ref: ReferenceType,
  ) => {
    // Set the `checked` state to the value of `defaultChecked` if it is truthy,
    // or the value of `isChecked` if it is truthy, or `false` if neither
    // is truthy.
    const { state, onChange, isDisabled: contextIsDisabled } = usePromoCardContext();
    const [checked, setChecked] = useState(
      type === 'checkbox' ? (defaultChecked ?? isChecked ?? false) : false,
    );

    const handleClick = () => {
      if (type === 'radio') {
        onChange(value || ''); // Update the context state for radio
      } else if (type === 'checkbox') {
        setChecked(!checked); // Update local state for checkbox
      }
    };

    const fallbackId = useId();
    const componentId = id || fallbackId;

    // Set the icon to `'arrow'` if `href` is truthy and `type` is falsy, or
    // `'download'` if `download` is truthy. If neither condition is true, set
    // `icon` to `undefined`.

    // Create a function to get icon type
    const getIconType = () => {
      if (indicatorIcon) {
        return indicatorIcon;
      }

      if (download) {
        return 'download';
      }

      if (href && !type) {
        return 'arrow';
      }

      return undefined;
    };

    const CardTitle = useDisplayFont ? Display : Title;

    // Define all class names string based on the values of the `href`, `type`,
    // `checked`, and `className` props.
    const commonClasses = clsx(
      {
        'np-Card--promoCard': true,
        'np-Card--checked': !href && type,
        'np-Card--link': href && !type,
        'is-checked':
          type === 'radio' ? value === state : type === 'checkbox' ? checked : undefined,
      },
      className,
    );

    // Object with common props that will be passed to the `Card` components
    const commonProps = {
      className: commonClasses,
      id: componentId,
      isDisabled: isDisabled || contextIsDisabled,
      onClick,
      onKeyDown,
      ref,
      'data-testid': testId,
      isSmall,
    };

    // Object with Anchor props that will be passed to the `a` element. These
    // won't be refurned if set to `isDisabled`
    const anchorProps =
      href && !isDisabled
        ? {
            download,
            href: href || undefined,
            hrefLang,
            rel,
            target,
            ref: anchorRef,
            id: anchorId,
          }
        : {};

    // Object of all Checked props that will be passed to the root `Card` component
    const checkedProps =
      (type === 'checkbox' || type === 'radio') && !href
        ? {
            ...commonProps,
            'aria-checked':
              type === 'radio' ? value === state : type === 'checkbox' ? checked : undefined,
            'aria-describedby': `${componentId}-title`,
            'aria-disabled': isDisabled,
            'data-value': value ?? undefined,
            role: type === 'checkbox' || type === 'radio' ? type : undefined,
            onClick: handleClick,
            onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {
              if (event.key === 'Enter' || event.key === ' ') {
                handleClick();
              }
            },
            ref,
            tabIndex: 0,
          }
        : {};

    const getTitle = () => {
      const titleContent =
        href && !type ? (
          <a className="np-Card-titleLink" {...anchorProps}>
            {title}
          </a>
        ) : (
          title
        );

      const titleProps = {
        id: `${componentId}-title`,
        as: headingLevel,
        className: 'np-Card-title',
      };

      return useDisplayFont ? (
        <Display type={Typography.DISPLAY_SMALL} {...titleProps}>
          {titleContent}
        </Display>
      ) : (
        <Title type={Typography.TITLE_SUBSECTION} {...titleProps}>
          {titleContent}
        </Title>
      );
    };

    useEffect(() => {
      setChecked(defaultChecked ?? isChecked ?? false);
    }, [defaultChecked, isChecked]);

    return (
      <BaseCard {...commonProps} {...checkedProps} {...props}>
        {(value === state || checked) && (
          <span className="np-Card-check">
            <Check size={24} aria-hidden="true" />
          </span>
        )}

        {getTitle()}

        <Body className="np-Card-description">{description}</Body>

        {imageSource && (
          <div className={clsx('np-Card-image', { imageClass })}>
            <Image src={imageSource} alt={imageAlt || ''} loading="lazy" />
          </div>
        )}

        <PromoCardIndicator label={indicatorLabel} icon={getIconType()} isSmall={isSmall} />
      </BaseCard>
    );
  },
) as PolymorphicPromoCard;

export default React.memo(PromoCard);
