import { useEffect, useState } from 'react';
import type { HTMLAttributes, ReactNode } from 'react';

import { WDS_LIVE_REGION_DELAY_MS } from '../constants';

export type AriaLive = 'off' | 'polite' | 'assertive';

type LivePoliteness = Exclude<AriaLive, 'off'>;

const LIVE_REGION_ROLE_BY_POLITENESS: Record<LivePoliteness, 'status' | 'alert'> = {
  assertive: 'alert',
  polite: 'status',
};

let nextPoliteAnnouncementAt = 0;
let nextAssertiveAnnouncementAt = 0;

const getNextAnnouncementAt = (politeness: LivePoliteness): number => {
  if (politeness === 'polite') {
    return nextPoliteAnnouncementAt;
  }

  return nextAssertiveAnnouncementAt;
};

const setNextAnnouncementAt = (politeness: LivePoliteness, value: number): void => {
  if (politeness === 'polite') {
    nextPoliteAnnouncementAt = value;
    return;
  }

  nextAssertiveAnnouncementAt = value;
};

export const resetLiveRegionAnnouncementQueue = (): void => {
  nextPoliteAnnouncementAt = 0;
  nextAssertiveAnnouncementAt = 0;
};

const calcAnnouncementDelayMs = (politeness: LivePoliteness, now: number): number => {
  return Math.max(now + WDS_LIVE_REGION_DELAY_MS, getNextAnnouncementAt(politeness)) - now;
};

const scheduleAnnouncement = (politeness: LivePoliteness): number => {
  const now = Date.now();
  const delayMs = calcAnnouncementDelayMs(politeness, now);
  setNextAnnouncementAt(politeness, now + delayMs + WDS_LIVE_REGION_DELAY_MS);
  return delayMs;
};

export interface LiveRegionProps extends Omit<
  HTMLAttributes<HTMLDivElement>,
  'role' | 'aria-live' | 'aria-atomic'
> {
  /**
   * Determines urgency: 'assertive' interrupts, 'polite' waits for idle, 'off' disables live region.
   */
  'aria-live': AriaLive;
  /** Optional stable key that triggers a new announcement when it changes. */
  announceOnChange?: string | number;
  /** Test ID for testing tools */
  'data-testid'?: string;
  children?: ReactNode;
}

/**
 * Renders an ARIA live region with the correct implicit role.
 *
 * - `aria-live="polite"` → `role="status"`
 * - `aria-live="assertive"` → `role="alert"`
 * - `aria-live="off"` → no live region (renders children unwrapped)
 *
 * The `role` prop is intentionally excluded from the public API
 * to prevent mismatches between `aria-live` and `role`.
 */
export const LiveRegion = ({
  'aria-live': ariaLive,
  announceOnChange,
  children,
  className,
  ...props
}: LiveRegionProps) => {
  const [shouldAnnounce, setShouldAnnounce] = useState(false);
  const announcementTrigger =
    announceOnChange ??
    (typeof children === 'string' || typeof children === 'number' ? children : undefined);

  useEffect(() => {
    if (ariaLive === 'off') {
      return;
    }

    // Reset announcement state, then schedule new announcement
    // This two-step process ensures screen readers re-announce the content
    // eslint-disable-next-line react-hooks/set-state-in-effect -- Deliberately resetting state to force re-announcement for screen readers
    setShouldAnnounce(false);

    const timeoutId = window.setTimeout(
      () => setShouldAnnounce(true),
      scheduleAnnouncement(ariaLive),
    );

    return () => window.clearTimeout(timeoutId);
  }, [ariaLive, announcementTrigger]);

  if (ariaLive === 'off') {
    return <>{children}</>;
  }

  return (
    <div
      role={LIVE_REGION_ROLE_BY_POLITENESS[ariaLive]}
      aria-live={ariaLive}
      aria-atomic="true"
      className={`wds-LiveRegion ${className ?? ''}`}
      {...props}
    >
      <div aria-hidden={shouldAnnounce ? undefined : 'true'}>{children}</div>
    </div>
  );
};
