import {
  type CSSProperties,
  type FC,
  type ReactNode,
  type RefObject,
} from 'react';
import classnames from 'classnames';

import type { WithSpacingProps } from '~/src/lib/hocs/withSpacing';

import withSpacing from '~/src/lib/hocs/withSpacing';

const FONT_SIZES = {
  small: 10,
  medium: 14,
  large: 18,
  'x-large': 22,
};

const FONT_WEIGHTS = {
  regular: 400,
  medium: 500,
  bold: 700,
};

type TextSizeTypes = 'small' | 'medium' | 'large' | 'x-large' | number | string;
type TextWeightTypes = 'regular' | 'medium' | 'bold';

export interface TextProps extends WithSpacingProps {
  size?: TextSizeTypes;
  maxSize?: TextSizeTypes;
  minSize?: TextSizeTypes;
  family?: string;

  // isCentered conflicts with withSpacing() HOC
  centered?: boolean;
  balance?: boolean;

  // deprecated
  bold?: boolean;
  inline?: boolean;

  isBold?: boolean;
  isInline?: boolean;
  isItalic?: boolean;
  isUnderline?: boolean;
  noSelection?: boolean;
  breakWord?: boolean;

  opacity?: number;
  letterSpacing?: number;
  isParagraph?: boolean;
  lineHeight?: string | number;
  style?: CSSProperties;
  withEllipsis?: boolean;
  noWrap?: boolean;
  weight?: TextWeightTypes;
  className?: string;
  align?: CSSProperties['textAlign'];
  tag?: 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span' | 'time' | 'li';
  textTransform?: CSSProperties['textTransform'];
  lineClamp?: number;
  hyphenate?: boolean;
  shadow?: string;
  color?: string;
  nodeRef?: RefObject<any>;
  testId?: string;
  title?: string;
  children: ReactNode;
}

// TODO: use stylesheet to prevent this bloating html payloads with inline styles
const TextInternal: FC<TextProps> = ({
  size = '1em',
  maxSize,
  minSize,
  children,

  // isCentered conflicts with withSpacing() HOC
  centered,
  balance,
  family,

  // deprecated
  bold,
  inline,

  isBold,
  isInline,
  isItalic,
  isUnderline,
  breakWord,

  opacity,
  letterSpacing,
  isParagraph,
  lineHeight,
  style,
  withEllipsis,
  noWrap,
  weight = '',
  className,
  align,
  tag = 'div',
  textTransform,
  testId,
  lineClamp,
  hyphenate,
  noSelection,
  shadow,
  color,
  nodeRef,
  title,
}) => {
  // back-compat
  isBold = isBold || bold;
  isInline = isInline || inline;

  if (isParagraph) tag = 'p';
  if (isInline) tag = 'span';

  const fontWeight = isBold ? FONT_WEIGHTS.bold : FONT_WEIGHTS[weight];
  const fontStyle = isItalic ? 'italic' : undefined;
  let fontSize = resolveFontSize(size);

  if (maxSize && minSize) {
    fontSize = `min(${resolveFontSize(maxSize)}, max(${resolveFontSize(
      minSize
    )}, ${fontSize}))`;
  } else if (maxSize) {
    fontSize = `min(${resolveFontSize(maxSize)}, ${fontSize})`;
  } else if (minSize) {
    fontSize = `max(${resolveFontSize(minSize)}, ${fontSize})`;
  }

  const styleInternal = {
    ...style,
    fontSize,
    fontWeight,
    fontStyle,
    opacity,
    color,
  };

  styleInternal.textAlign = centered ? 'center' : align;
  styleInternal.textTransform = textTransform;

  if (lineHeight || isParagraph || withEllipsis) {
    styleInternal.lineHeight = isParagraph ? lineHeight || 1.5 : lineHeight;
    if (withEllipsis) styleInternal.height = styleInternal.lineHeight;
  }

  if (noWrap) styleInternal.whiteSpace = 'nowrap';
  if (shadow) styleInternal.textShadow = shadow;
  if (isUnderline) styleInternal.borderBottom = 'dotted 1px';
  if (noSelection) styleInternal.userSelect = 'none';
  if (balance) (styleInternal as any).textWrap = 'balance';
  if (breakWord) styleInternal.wordBreak = 'break-word';

  // if the given font family is not defined/loaded it will fallback to
  // `--default-font` defined in NextApp/styles.ts while the custom font is loading
  if (family) {
    styleInternal.fontFamily = `${family}, var(--default-font)`;
  }

  if (letterSpacing !== undefined) {
    styleInternal.letterSpacing = `${letterSpacing}em`;
  }

  // multiline ellipsis effect
  // https://css-tricks.com/almanac/properties/l/line-clamp/
  if (lineClamp) {
    styleInternal.display = '-webkit-box';
    styleInternal.WebkitLineClamp = lineClamp;
    styleInternal.WebkitBoxOrient = 'vertical';
    styleInternal.overflow = 'hidden';
  }

  // cast tag string into something typescript understands
  const Tag = tag;

  return (
    <Tag
      ref={nodeRef}
      style={styleInternal}
      title={title}
      data-testid={testId}
      className={classnames(className, weight, {
        withEllipsis,
        hyphenate,
      })}
    >
      {children}
      <style jsx>{`
         {
          line-height: 1.1em;
          text-rendering: optimizeLegibility;
          -webkit-font-smoothing: antialiased;
          letter-spacing: 0.01em;
          word-break: break-word;
          cursor: default;
        }

        .hyphenate {
          hyphens: auto;
        }

        .withEllipsis {
          text-overflow: ellipsis;
          overflow: hidden;
          white-space: nowrap;
        }

        .medium {
          font-weight: 500;
        }

        .bold {
          font-weight: 700;
        }
      `}</style>
    </Tag>
  );
};

const resolveFontSize = (value) => {
  // it's a pixel number
  if (typeof value === 'number') return `${value}px`;

  // its an alias size
  if (FONT_SIZES[value]) return FONT_SIZES[value];

  // it's an non-px size
  if (/(em|vw|vh|%)$/.test(value)) return value;

  // default / fallback to inherit from current context
  return '1em';
};

const Text = withSpacing(TextInternal);

export default Text;
