import { ElementType, ReactNode, useId, useState } from 'react';
import { clsx } from 'clsx';

import Body from '../../body';
import Button from '../../button';
import Container from '../../container';
import { Breakpoint, LiveRegion, Typography } from '../../common';
import { ButtonProps } from '../../button/Button.types';
import { PrimitivePrompt, PrimitivePromptProps } from '../PrimitivePrompt';

import { renderPromptMedia, PromptMedia } from '../helpers/promptMedia';
import { ExpanderToggle } from '../common/Expander/Expander';
import { buildAnnouncementString } from './helpers';
import { useContainerSize } from '../../common/hooks/useContainerSize';

export type CriticalBannerProps = {
  title?: ReactNode;
  description: ReactNode;
  media?: PromptMedia;
  action?: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
    label: ButtonProps['children'];
  };
  actionSecondary?: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
    label: ButtonProps['children'];
  };
  sentiment?: Exclude<PrimitivePromptProps['sentiment'], 'proposition'>;
  /**
   * Controls whether the description and actions are visible, as a controlled component.
   * When collapsed with a title, only the title is shown.
   * When collapsed without a title, the description is trimmed to 2 lines.
   *
   * Note: On desktop (container width >= 768px), the banner is always expanded
   * regardless of this prop value.
   */
  expanded?: boolean;
  /**
   * Called when the user clicks the chevron toggle.
   * If not provided, the component will manage expanded state internally.
   */
  onToggle?: () => void;
} & Pick<PrimitivePromptProps, 'id' | 'className' | 'data-testid'>;

/**
 * A full-width, non-dismissible banner for critical messages such as account blocks or
 * time-sensitive actions that require immediate user attention.

 * @see {@link https://docs.wise.design/components/critical-banner Design Spec}
 */
export const CriticalBanner = ({
  sentiment = 'negative',
  title,
  description,
  media = {},
  action,
  actionSecondary,
  expanded: expandedProp,
  onToggle,
  id,
  className,
  'data-testid': testId,
}: CriticalBannerProps) => {
  const [containerRef, isDesktop] = useContainerSize(Breakpoint.MEDIUM);
  const isControlled = expandedProp !== undefined && onToggle !== undefined;
  const [internalExpanded, setInternalExpanded] = useState(true);
  const resolvedExpanded = isDesktop ? true : isControlled ? expandedProp : internalExpanded;
  const handleToggle = isControlled
    ? onToggle
    : () => setInternalExpanded((previousExpanded) => !previousExpanded);
  const hasActions = action ?? actionSecondary;
  const mediaId = useId();
  const titleId = useId();
  const descId = useId();

  const ariaLabelledByIds = [
    media['aria-hidden'] ? undefined : mediaId,
    !title ? undefined : titleId,
  ]
    .filter(Boolean)
    .join(' ');

  const bannerSurface = (
    <Container
      ref={containerRef}
      size="fluid"
      as={PrimitivePrompt as ElementType}
      id={id}
      sentiment={sentiment}
      emphasis={sentiment === 'neutral' ? 'base' : 'elevated'}
      data-testid={testId}
      className={clsx(
        'wds-critical-banner',
        'wds-critical-banner-overhang',
        {
          'wds-critical-banner--collapsed': !resolvedExpanded,
          'wds-critical-banner--with-two-actions': !!actionSecondary,
        },
        className,
      )}
      media={renderPromptMedia({
        media,
        sentiment,
        mediaId,
        imgClassName: 'wds-critical-banner--media-image',
      })}
      actions={
        hasActions ? (
          <div aria-hidden={!resolvedExpanded ? true : undefined} style={{ display: 'contents' }}>
            {actionSecondary && (
              // @ts-expect-error onClick type mismatch
              <Button
                v2
                size="md"
                priority="secondary"
                href={actionSecondary.href}
                tabIndex={resolvedExpanded ? undefined : -1}
                onClick={actionSecondary?.onClick}
              >
                {actionSecondary.label}
              </Button>
            )}
            {action && (
              // @ts-expect-error onClick type mismatch
              <Button
                v2
                size="md"
                priority="primary"
                href={action.href}
                tabIndex={resolvedExpanded ? undefined : -1}
                onClick={action.onClick}
              >
                {action.label}
              </Button>
            )}
          </div>
        ) : undefined
      }
      role="region"
      aria-labelledby={ariaLabelledByIds || undefined}
      aria-describedby={description ? descId : undefined}
    >
      <div className="wds-critical-banner__text-wrapper">
        {title && (
          <Body
            id={titleId}
            type={Typography.BODY_LARGE_BOLD}
            className="wds-critical-banner__content wds-critical-banner__title"
          >
            {title}
          </Body>
        )}
        {description && (
          <Body
            id={descId}
            className={clsx('wds-critical-banner__content', 'wds-critical-banner__description', {
              'wds-critical-banner__description--with-title': !!title,
            })}
          >
            {description}
          </Body>
        )}
      </div>
      <ExpanderToggle
        expanded={resolvedExpanded}
        size={24}
        className="wds-critical-banner__toggle"
        onToggle={handleToggle}
      />
    </Container>
  );

  return (
    <LiveRegion
      aria-live="assertive"
      announceOnChange={buildAnnouncementString({
        title,
        description,
        expanded: resolvedExpanded,
        actionLabel: action?.label,
        actionSecondaryLabel: actionSecondary?.label,
      })}
    >
      <div className="wds-critical-banner-overhang-query">
        <div className="wds-critical-banner__entry-mask">
          <div className="wds-critical-banner__entry-track">{bannerSurface}</div>
        </div>
      </div>
    </LiveRegion>
  );
};

export default CriticalBanner;
