// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import Measure from 'react-measure';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import classNames from 'classnames';

import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
import type { TextAttachmentType } from '../types/Attachment';
import { AddNewLines } from './conversation/AddNewLines';
import { Emojify } from './conversation/Emojify';
import { StoryLinkPreview } from './StoryLinkPreview';
import { TextAttachmentStyleType } from '../types/Attachment';
import { count } from '../util/grapheme';
import { getDomain } from '../types/LinkPreview';
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
import {
  COLOR_WHITE_INT,
  getHexFromNumber,
  getBackgroundColor,
} from '../util/getStoryBackground';
import { SECOND } from '../util/durations';
import { useRefMerger } from '../hooks/useRefMerger';

const renderNewLines: RenderTextCallbackType = ({
  text: textWithNewLines,
  key,
}) => {
  return <AddNewLines key={key} text={textWithNewLines} />;
};

const CHAR_LIMIT_TEXT_LARGE = 50;
const CHAR_LIMIT_TEXT_MEDIUM = 200;
const FONT_SIZE_LARGE = 59;
const FONT_SIZE_MEDIUM = 42;
const FONT_SIZE_SMALL = 32;

enum TextSize {
  Small,
  Medium,
  Large,
}

export type PropsType = {
  disableLinkPreviewPopup?: boolean;
  i18n: LocalizerType;
  isEditingText?: boolean;
  isThumbnail?: boolean;
  onChange?: (text: string) => unknown;
  onClick?: () => unknown;
  onRemoveLinkPreview?: () => unknown;
  textAttachment: TextAttachmentType;
};

function getTextSize(text: string): TextSize {
  const length = count(text);

  if (length < CHAR_LIMIT_TEXT_LARGE) {
    return TextSize.Large;
  }

  if (length < CHAR_LIMIT_TEXT_MEDIUM) {
    return TextSize.Medium;
  }

  return TextSize.Small;
}

function getFont(
  text: string,
  textSize: TextSize,
  textStyle?: TextAttachmentStyleType | null,
  i18n?: LocalizerType
): string {
  const textStyleIndex = Number(textStyle) || 0;
  const fontName = getFontNameByTextScript(text, textStyleIndex, i18n);

  let fontSize = FONT_SIZE_SMALL;
  switch (textSize) {
    case TextSize.Large:
      fontSize = FONT_SIZE_LARGE;
      break;
    case TextSize.Medium:
      fontSize = FONT_SIZE_MEDIUM;
      break;
    default:
      fontSize = FONT_SIZE_SMALL;
  }

  const fontWeight = textStyle === TextAttachmentStyleType.BOLD ? 'bold ' : '';

  return `${fontWeight}${fontSize}px ${fontName}`;
}

function getTextStyles(
  textContent: string,
  textForegroundColor?: number | null,
  textStyle?: TextAttachmentStyleType | null,
  i18n?: LocalizerType
): { color: string; font: string; textAlign: 'left' | 'center' } {
  return {
    color: getHexFromNumber(textForegroundColor || COLOR_WHITE_INT),
    font: getFont(textContent, getTextSize(textContent), textStyle, i18n),
    textAlign: getTextSize(textContent) === TextSize.Small ? 'left' : 'center',
  };
}

export const TextAttachment = forwardRef<HTMLTextAreaElement, PropsType>(
  function TextAttachmentForwarded(
    {
      disableLinkPreviewPopup,
      i18n,
      isEditingText,
      isThumbnail,
      onChange,
      onClick,
      onRemoveLinkPreview,
      textAttachment,
    },
    forwardedTextEditorRef
  ): JSX.Element | null {
    const linkPreview = useRef<HTMLDivElement | null>(null);
    const [linkPreviewOffsetTop, setLinkPreviewOffsetTop] = useState<
      number | undefined
    >();

    const textContent = textAttachment.text || '';
    const textEditorRef = useRef<HTMLTextAreaElement | null>(null);
    const refMerger = useRefMerger();

    useEffect(() => {
      const node = textEditorRef?.current;
      if (!node) {
        return;
      }

      node.focus();
      node.setSelectionRange(node.value.length, node.value.length);
    }, [isEditingText]);

    useEffect(() => {
      setLinkPreviewOffsetTop(undefined);
    }, [textAttachment.preview?.url]);

    const [isHoveringOverTooltip, setIsHoveringOverTooltip] = useState(false);

    function showTooltip() {
      if (disableLinkPreviewPopup) {
        return;
      }
      setIsHoveringOverTooltip(true);
      setLinkPreviewOffsetTop(linkPreview?.current?.offsetTop);
    }

    useEffect(() => {
      const timeout = setTimeout(() => {
        if (!isHoveringOverTooltip) {
          setLinkPreviewOffsetTop(undefined);
        }
      }, 5 * SECOND);

      return () => {
        clearTimeout(timeout);
      };
    }, [isHoveringOverTooltip]);

    const storyBackgroundColor = {
      background: getBackgroundColor(textAttachment),
    };

    return (
      <Measure bounds>
        {({ contentRect, measureRef }) => {
          const scaleFactor = (contentRect.bounds?.height || 1) / 1280;

          return (
            // eslint-disable-next-line jsx-a11y/no-static-element-interactions
            <div
              className="TextAttachment"
              onClick={() => {
                if (linkPreviewOffsetTop) {
                  setLinkPreviewOffsetTop(undefined);
                }
                onClick?.();
              }}
              onKeyUp={ev => {
                if (ev.key === 'Escape' && linkPreviewOffsetTop) {
                  setLinkPreviewOffsetTop(undefined);
                }
              }}
              ref={measureRef}
              style={isThumbnail ? storyBackgroundColor : undefined}
            >
              {/*
            The tooltip must be outside of the scaled area, as it should not scale with
            the story, but it must be positioned using the scaled offset
            */}
              {textAttachment.preview &&
                textAttachment.preview.url &&
                linkPreviewOffsetTop &&
                !isThumbnail && (
                  <a
                    className="TextAttachment__preview__tooltip"
                    href={textAttachment.preview.url}
                    rel="noreferrer"
                    style={{
                      top: linkPreviewOffsetTop * scaleFactor - 89, // minus height of tooltip and some spacing
                    }}
                    target="_blank"
                  >
                    <div>
                      <div className="TextAttachment__preview__tooltip__title">
                        {i18n('TextAttachment__preview__link')}
                      </div>
                      <div className="TextAttachment__preview__tooltip__url">
                        {textAttachment.preview.url}
                      </div>
                    </div>
                    <div className="TextAttachment__preview__tooltip__arrow" />
                  </a>
                )}
              <div
                className="TextAttachment__story"
                style={{
                  ...(isThumbnail ? {} : storyBackgroundColor),
                  transform: `scale(${scaleFactor})`,
                }}
              >
                {(textContent || onChange) && (
                  <div
                    className={classNames('TextAttachment__text', {
                      'TextAttachment__text--with-bg': Boolean(
                        textAttachment.textBackgroundColor
                      ),
                    })}
                    style={{
                      backgroundColor: textAttachment.textBackgroundColor
                        ? getHexFromNumber(textAttachment.textBackgroundColor)
                        : 'transparent',
                    }}
                  >
                    {onChange ? (
                      <TextareaAutosize
                        className="TextAttachment__text__container TextAttachment__text__textarea"
                        disabled={!isEditingText}
                        onChange={ev => onChange(ev.currentTarget.value)}
                        placeholder={i18n('TextAttachment__placeholder')}
                        ref={refMerger(forwardedTextEditorRef, textEditorRef)}
                        style={getTextStyles(
                          textContent,
                          textAttachment.textForegroundColor,
                          textAttachment.textStyle,
                          i18n
                        )}
                        value={textContent}
                      />
                    ) : (
                      <div
                        className="TextAttachment__text__container"
                        style={getTextStyles(
                          textContent,
                          textAttachment.textForegroundColor,
                          textAttachment.textStyle,
                          i18n
                        )}
                      >
                        <Emojify
                          text={textContent}
                          renderNonEmoji={renderNewLines}
                        />
                      </div>
                    )}
                  </div>
                )}
                {textAttachment.preview && textAttachment.preview.url && (
                  <div
                    className={classNames('TextAttachment__preview-container', {
                      'TextAttachment__preview-container--large': Boolean(
                        textAttachment.preview.title
                      ),
                    })}
                    ref={linkPreview}
                    onBlur={() => setIsHoveringOverTooltip(false)}
                    onFocus={showTooltip}
                    onMouseOut={() => setIsHoveringOverTooltip(false)}
                    onMouseOver={showTooltip}
                  >
                    {onRemoveLinkPreview && (
                      <div className="TextAttachment__preview__remove">
                        <button
                          aria-label={i18n(
                            'Keyboard--remove-draft-link-preview'
                          )}
                          type="button"
                          onClick={onRemoveLinkPreview}
                        />
                      </div>
                    )}
                    <StoryLinkPreview
                      {...textAttachment.preview}
                      domain={getDomain(String(textAttachment.preview.url))}
                      forceCompactMode={
                        getTextSize(textContent) !== TextSize.Large
                      }
                      i18n={i18n}
                      title={textAttachment.preview.title || undefined}
                      url={textAttachment.preview.url}
                    />
                  </div>
                )}
              </div>
            </div>
          );
        }}
      </Measure>
    );
  }
);