import { forwardRef } from "react";
import clsx from "clsx";
import Link from "next/link";

import Icon, { type IconVariant } from "@/components/icon";
import { isExternalUrl } from "@/helpers/link";
import { extractTextFromReactNode } from "@/helpers/string";

import styles from "./styles.module.scss";

enum CallToActionStyles {
  Link = "link",
  LinkPrimary = "linkPrimary",
  LinkInline = "linkInline",
  Button = "button",
  ButtonPrimary = "buttonPrimary",
  ButtonSecondary = "buttonSecondary",
  ButtonPill = "buttonPill",
  PlayButton = "playButton",
  VersionButton = "versionButton",
  RestartButton = "restartButton",
}

export type CallToActionVariant = `${CallToActionStyles}`;

export interface CallToActionProps {
  href?: string;

  variant?: CallToActionVariant;
  icon?: IconVariant;
  iconLabel?: string;
  label?: string;
  tabIndex?: number;
  ariaLabel?: string;
  onClick?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
  onPointerEnter?: React.PointerEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  >;
  onPointerLeave?: React.PointerEventHandler<
    HTMLButtonElement | HTMLAnchorElement
  >;
  onFocus?: React.FocusEventHandler<HTMLButtonElement | HTMLAnchorElement>;
  children?: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
  download?: string;
}

const CallToAction = forwardRef<
  HTMLAnchorElement | HTMLButtonElement,
  CallToActionProps
>(
  (
    {
      href,
      onClick,
      onFocus,
      variant,
      icon,
      iconLabel,
      label,
      tabIndex,
      ariaLabel,
      children,
      className,
      download,
      ...props
    },
    ref,
  ) => {
    const isExternalLink = href && isExternalUrl(href);
    const isInternalLink = href && !isExternalUrl(href);
    const isMailtoLink = href && href.startsWith("mailto:");
    const isButton = !isExternalLink && !isInternalLink;
    const inferredVariant = variant
      ? variant
      : isExternalLink || isInternalLink
        ? CallToActionStyles.Link
        : CallToActionStyles.Button;

    let ariaLabelWithFallback =
      ariaLabel ?? label ?? extractTextFromReactNode(children);

    if (href === "/") {
      ariaLabelWithFallback += "Go to the homepage";
    } else if (isExternalLink) {
      ariaLabelWithFallback += " (opens in a new window)";
    } else if (isMailtoLink) {
      ariaLabelWithFallback = `Send an email to ${href.replace("mailto:", "")}`;
    } else if (iconLabel) {
      ariaLabelWithFallback += ` ${iconLabel}`;
    }

    const ctaClasses = clsx(styles.callToAction, className, {
      [styles.hasIcon]: icon,
      [styles[inferredVariant]]: inferredVariant,
    });

    const inner = (
      <span className={styles.inner} aria-label={label}>
        {children}
        {icon && (
          <Icon
            className={styles.icon}
            variant={icon}
            aria-label={ariaLabelWithFallback}
            {...props}
          />
        )}
        {label}
      </span>
    );

    if (isExternalLink) {
      return (
        <a
          ref={ref as React.Ref<HTMLAnchorElement>}
          className={ctaClasses}
          href={href}
          target="_blank"
          tabIndex={tabIndex || 0}
          onFocus={onFocus}
          rel="noopener noreferrer"
          onClick={onClick}
          aria-label={ariaLabel}
          {...(download ? { download } : {})}
          {...props}
        >
          {inner}
        </a>
      );
    }

    if (isInternalLink) {
      return (
        <Link
          ref={ref as React.Ref<HTMLAnchorElement>}
          className={ctaClasses}
          href={href}
          tabIndex={tabIndex || 0}
          onFocus={onFocus}
          aria-label={ariaLabelWithFallback}
          {...props}
        >
          {inner}
        </Link>
      );
    }

    if (isButton) {
      return (
        <button
          ref={ref as React.Ref<HTMLButtonElement>}
          className={ctaClasses}
          onClick={onClick}
          onFocus={onFocus}
          aria-label={ariaLabelWithFallback}
          tabIndex={tabIndex || 0}
          {...props}
        >
          {inner}
        </button>
      );
    }

    return null;
  },
);
CallToAction.displayName = "CallToAction";

export default CallToAction;
