// @flow strict

import * as React from 'react';

import type {ColorTypes} from '../../types/typography';
import {TEXT_COLORS} from '../../types/typography';
import classify from '../../utils/classify';
import {escapeRegExp} from '../../utils/string';

import css from '../../styles/typography.module.css';


export type TextProps = {
  color?: ColorTypes,
  children?: React.Node,
  className?: string,
  highlightedTextClassName?: string,
  highlightString?: string | string[],
  caseSensitiveHighlighting?: boolean,
  highlightWithBackground?: boolean,
  ...
};

export type HighlightTextProps = {
  text: string,
  highlight: string | string[],
  highlightClassName?: string,
  caseSensitiveHighlighting?: boolean,
  highlightWithBackground?: boolean,
};

const HighlightText: React$AbstractComponent<HighlightTextProps, HTMLSpanElement> =
React.forwardRef<HighlightTextProps, HTMLSpanElement>( ({
  text,
  highlight,
  highlightClassName,
  caseSensitiveHighlighting,
  highlightWithBackground,
}: HighlightTextProps, ref) => {
  // Split text on highlight term, include term itself into parts, ignore case
  let highlights = '';
  if (Array.isArray(highlight)) {
    highlights = highlight.map(escapeRegExp).join('|');
  } else {
    highlights = escapeRegExp(highlight);
  }

  const parts = text
    .split(new RegExp(`(${highlights})`, caseSensitiveHighlighting ? '' : 'gi'))
    .filter((part) => part !== '');

  return (
    <span ref={ref}>
      {parts.map((part, idx) =>
        highlights.toLowerCase().includes(escapeRegExp(part).toLowerCase()) ? (
          <span
            // eslint-disable-next-line react/no-array-index-key
            key={idx}
            className={classify(
              css.highlightText,
              {
                [css.bgHighlighting]: highlightWithBackground,
              },
              highlightClassName,
            )}
          >
            {part}
          </span>
        ) : (
          part
        ),
      )}
    </span>
  );
}
)

export const JumboMedium: React$AbstractComponent<TextProps, HTMLSpanElement> =
  React.forwardRef<TextProps, HTMLSpanElement>(
    (
      {
        color = TEXT_COLORS.primary,
        children,
        className,
        highlightedTextClassName,
        highlightString,
        caseSensitiveHighlighting,
        highlightWithBackground,
        ...props
      }: TextProps,
      ref,
    ): React.Node => (
      <span
        {...props}
        className={classify(css.jumboMedium, css[color], className)}
        ref={ref}
      >
        {!!highlightString?.length && typeof children === 'string' ? (
          <HighlightText
            text={children}
            highlight={highlightString}
            caseSensitiveHighlighting={caseSensitiveHighlighting}
            highlightClassName={highlightedTextClassName}
            highlightWithBackground={highlightWithBackground}
          />
        ) : (
          children
        )}
      </span>
    ),
  );

export const TitleMedium: React$AbstractComponent<
  TextProps,
  HTMLHeadingElement,
> = React.forwardRef<TextProps, HTMLHeadingElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <h1
      {...props}
      className={classify(css.titleMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </h1>
  ),
);

export const SubTitleLarge: React$AbstractComponent<
  TextProps,
  HTMLHeadingElement,
> = React.forwardRef<TextProps, HTMLHeadingElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <h2
      {...props}
      className={classify(css.subTitleLarge, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </h2>
  ),
);

export const SubTitleMedium: React$AbstractComponent<
  TextProps,
  HTMLHeadingElement,
> = React.forwardRef<TextProps, HTMLHeadingElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <h3
      {...props}
      className={classify(css.subTitleMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </h3>
  ),
);

export const SubTitleSmall: React$AbstractComponent<
  TextProps,
  HTMLHeadingElement,
> = React.forwardRef<TextProps, HTMLHeadingElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <h4
      {...props}
      className={classify(css.subTitleSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </h4>
  ),
);

export const SubTitleExtraSmall: React$AbstractComponent<
  TextProps,
  HTMLHeadingElement,
> = React.forwardRef<TextProps, HTMLHeadingElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <h5
      {...props}
      className={classify(css.subTitleExtraSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </h5>
  ),
);

export const ButtonTextMedium: React$AbstractComponent<
  TextProps,
  HTMLSpanElement,
> = React.forwardRef<TextProps, HTMLSpanElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <span
      {...props}
      className={classify(css.buttonTextMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </span>
  ),
);

export const ButtonTextSmall: React$AbstractComponent<
  TextProps,
  HTMLSpanElement,
> = React.forwardRef<TextProps, HTMLSpanElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <span
      {...props}
      className={classify(css.buttonTextSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </span>
  ),
);

export const ButtonTextExtraSmall: React$AbstractComponent<
  TextProps,
  HTMLSpanElement,
> = React.forwardRef<TextProps, HTMLSpanElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <span
      {...props}
      className={classify(css.buttonTextExtraSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </span>
  ),
);

export const FormInputMedium: React$AbstractComponent<
  TextProps,
  HTMLParagraphElement,
> = React.forwardRef<TextProps, HTMLParagraphElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <p
      {...props}
      className={classify(css.formInputMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </p>
  ),
);

export const FormInputSmall: React$AbstractComponent<
  TextProps,
  HTMLParagraphElement,
> = React.forwardRef<TextProps, HTMLParagraphElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <p
      {...props}
      className={classify(css.formInputSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </p>
  ),
);

export const BodyLarge: React$AbstractComponent<
  TextProps,
  HTMLParagraphElement,
> = React.forwardRef<TextProps, HTMLParagraphElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <p
      {...props}
      className={classify(css.bodyLarge, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </p>
  ),
);

export const BodyMedium: React$AbstractComponent<
  TextProps,
  HTMLParagraphElement,
> = React.forwardRef<TextProps, HTMLParagraphElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <p
      {...props}
      className={classify(css.bodyMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </p>
  ),
);

export const BodySmall: React$AbstractComponent<
  TextProps,
  HTMLParagraphElement,
> = React.forwardRef<TextProps, HTMLParagraphElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <p
      {...props}
      className={classify(css.bodySmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </p>
  ),
);

export const FormLabelMedium: React$AbstractComponent<
  TextProps,
  HTMLSpanElement,
> = React.forwardRef<TextProps, HTMLSpanElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <span
      {...props}
      className={classify(css.formLabelMedium, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </span>
  ),
);

export const FormLabelSmall: React$AbstractComponent<
  TextProps,
  HTMLSpanElement,
> = React.forwardRef<TextProps, HTMLSpanElement>(
  (
    {
      color = TEXT_COLORS.primary,
      children,
      className,
      highlightedTextClassName,
      highlightString,
      caseSensitiveHighlighting,
      highlightWithBackground,
      ...props
    }: TextProps,
    ref,
  ): React.Node => (
    <span
      {...props}
      className={classify(css.formLabelSmall, css[color], className)}
      ref={ref}
    >
      {!!highlightString?.length && typeof children === 'string' ? (
        <HighlightText
          text={children}
          highlight={highlightString}
          caseSensitiveHighlighting={caseSensitiveHighlighting}
          highlightClassName={highlightedTextClassName}
          highlightWithBackground={highlightWithBackground}
        />
      ) : (
        children
      )}
    </span>
  ),
);
