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

import FocusTrap from 'focus-trap-react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { get, has, noop } from 'lodash';
import { usePopper } from 'react-popper';

import type { EmojiPickDataType } from './emoji/EmojiPicker';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { LocalizerType } from '../types/Util';
import type { Props as EmojiButtonPropsType } from './emoji/EmojiButton';
import type { TextAttachmentType } from '../types/Attachment';

import { Button, ButtonVariant } from './Button';
import { ContextMenu } from './ContextMenu';
import { EmojiButton } from './emoji/EmojiButton';
import { LinkPreviewSourceType, findLinks } from '../types/LinkPreview';
import type { MaybeGrabLinkPreviewOptionsType } from '../types/LinkPreview';
import { Input } from './Input';
import { Slider } from './Slider';
import { StoryLinkPreview } from './StoryLinkPreview';
import { TextAttachment } from './TextAttachment';
import { Theme, themeClassName } from '../util/theme';
import { getRGBA, getRGBANumber } from '../mediaEditor/util/color';
import {
  COLOR_BLACK_INT,
  COLOR_WHITE_INT,
  getBackgroundColor,
} from '../util/getStoryBackground';
import { convertShortName } from './emoji/lib';
import { objectMap } from '../util/objectMap';
import { handleOutsideClick } from '../util/handleOutsideClick';
import { ConfirmDiscardDialog } from './ConfirmDiscardDialog';
import { Spinner } from './Spinner';

export type PropsType = {
  debouncedMaybeGrabLinkPreview: (
    message: string,
    source: LinkPreviewSourceType,
    options?: MaybeGrabLinkPreviewOptionsType
  ) => unknown;
  i18n: LocalizerType;
  isSending: boolean;
  linkPreview?: LinkPreviewType;
  onClose: () => unknown;
  onDone: (textAttachment: TextAttachmentType) => unknown;
  onUseEmoji: (_: EmojiPickDataType) => unknown;
} & Pick<EmojiButtonPropsType, 'onSetSkinTone' | 'recentEmojis' | 'skinTone'>;

enum LinkPreviewApplied {
  None = 'None',
  Automatic = 'Automatic',
  Manual = 'Manual',
}

enum TextStyle {
  Default,
  Regular,
  Bold,
  Serif,
  Script,
  Condensed,
}

enum TextBackground {
  None,
  Background,
  Inverse,
}

const BackgroundStyle = {
  BG1: { color: 4285041620 },
  BG2: { color: 4287006657 },
  BG3: { color: 4290019212 },
  BG4: { color: 4287205768 },
  BG5: { color: 4283667331 },
  BG6: {
    angle: 180,
    startColor: 4279871994,
    endColor: 4294951785,
  },
  BG7: {
    angle: 180,
    startColor: 4282660824,
    endColor: 4294938254,
  },
  BG8: {
    angle: 180,
    startColor: 4278206532,
    endColor: 4287871076,
  },
};

type BackgroundStyleType = typeof BackgroundStyle[keyof typeof BackgroundStyle];

function getBackground(
  bgStyle: BackgroundStyleType
): Pick<TextAttachmentType, 'color' | 'gradient'> {
  if (has(bgStyle, 'color')) {
    return { color: get(bgStyle, 'color') };
  }

  const angle = get(bgStyle, 'angle');
  const startColor = get(bgStyle, 'startColor');
  const endColor = get(bgStyle, 'endColor');

  return {
    gradient: { angle, startColor, endColor },
  };
}

function getBgButtonAriaLabel(
  i18n: LocalizerType,
  textBackground: TextBackground
): string {
  if (textBackground === TextBackground.Background) {
    return i18n('icu:StoryCreator__text-bg--background');
  }

  if (textBackground === TextBackground.Inverse) {
    return i18n('icu:StoryCreator__text-bg--inverse');
  }

  return i18n('icu:StoryCreator__text-bg--none');
}

export function TextStoryCreator({
  debouncedMaybeGrabLinkPreview,
  i18n,
  isSending,
  linkPreview,
  onClose,
  onDone,
  onSetSkinTone,
  onUseEmoji,
  recentEmojis,
  skinTone,
}: PropsType): JSX.Element {
  const [showConfirmDiscardModal, setShowConfirmDiscardModal] = useState(false);

  const onTryClose = useCallback(() => {
    setShowConfirmDiscardModal(true);
  }, [setShowConfirmDiscardModal]);

  const [isEditingText, setIsEditingText] = useState(false);
  const [selectedBackground, setSelectedBackground] =
    useState<BackgroundStyleType>(BackgroundStyle.BG1);
  const [textStyle, setTextStyle] = useState<TextStyle>(TextStyle.Regular);
  const [textBackground, setTextBackground] = useState<TextBackground>(
    TextBackground.None
  );
  const [sliderValue, setSliderValue] = useState<number>(100);
  const [text, setText] = useState<string>('');

  const [isColorPickerShowing, setIsColorPickerShowing] = useState(false);
  const [colorPickerPopperButtonRef, setColorPickerPopperButtonRef] =
    useState<HTMLButtonElement | null>(null);
  const [colorPickerPopperRef, setColorPickerPopperRef] =
    useState<HTMLDivElement | null>(null);

  const colorPickerPopper = usePopper(
    colorPickerPopperButtonRef,
    colorPickerPopperRef,
    {
      modifiers: [
        {
          name: 'arrow',
        },
      ],
      placement: 'top',
      strategy: 'fixed',
    }
  );

  const [linkPreviewApplied, setLinkPreviewApplied] = useState(
    LinkPreviewApplied.None
  );
  const hasLinkPreviewApplied = linkPreviewApplied !== LinkPreviewApplied.None;
  const [linkPreviewInputValue, setLinkPreviewInputValue] = useState('');

  useEffect(() => {
    if (!linkPreviewInputValue) {
      return;
    }
    if (linkPreviewApplied === LinkPreviewApplied.Manual) {
      return;
    }
    debouncedMaybeGrabLinkPreview(
      linkPreviewInputValue,
      LinkPreviewSourceType.StoryCreator,
      {
        mode: 'story',
      }
    );
  }, [
    debouncedMaybeGrabLinkPreview,
    linkPreviewApplied,
    linkPreviewInputValue,
  ]);

  useEffect(() => {
    if (!text) {
      return;
    }
    if (linkPreviewApplied === LinkPreviewApplied.Manual) {
      return;
    }
    debouncedMaybeGrabLinkPreview(text, LinkPreviewSourceType.StoryCreator);
  }, [debouncedMaybeGrabLinkPreview, linkPreviewApplied, text]);

  useEffect(() => {
    if (!linkPreview || !text) {
      return;
    }

    const links = findLinks(text);

    const shouldApplyLinkPreview = links.includes(linkPreview.url);
    setLinkPreviewApplied(oldValue => {
      if (oldValue === LinkPreviewApplied.Manual) {
        return oldValue;
      }
      if (shouldApplyLinkPreview) {
        return LinkPreviewApplied.Automatic;
      }
      return LinkPreviewApplied.None;
    });
  }, [linkPreview, text]);

  const [isLinkPreviewInputShowing, setIsLinkPreviewInputShowing] =
    useState(false);
  const [linkPreviewInputPopperButtonRef, setLinkPreviewInputPopperButtonRef] =
    useState<HTMLButtonElement | null>(null);
  const [linkPreviewInputPopperRef, setLinkPreviewInputPopperRef] =
    useState<HTMLDivElement | null>(null);

  const linkPreviewInputPopper = usePopper(
    linkPreviewInputPopperButtonRef,
    linkPreviewInputPopperRef,
    {
      modifiers: [
        {
          name: 'arrow',
        },
      ],
      placement: 'top',
      strategy: 'fixed',
    }
  );

  useEffect(() => {
    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        if (
          isColorPickerShowing ||
          isEditingText ||
          isLinkPreviewInputShowing
        ) {
          setIsColorPickerShowing(false);
          setIsEditingText(false);
          setIsLinkPreviewInputShowing(false);
        } else {
          onTryClose();
        }
        event.preventDefault();
        event.stopPropagation();
      }
    };

    const useCapture = true;
    document.addEventListener('keydown', handleEscape, useCapture);

    return () => {
      document.removeEventListener('keydown', handleEscape, useCapture);
    };
  }, [
    isColorPickerShowing,
    isEditingText,
    isLinkPreviewInputShowing,
    colorPickerPopperButtonRef,
    showConfirmDiscardModal,
    setShowConfirmDiscardModal,
    onTryClose,
  ]);

  useEffect(() => {
    if (!isColorPickerShowing) {
      return noop;
    }
    return handleOutsideClick(
      () => {
        setIsColorPickerShowing(false);
        return true;
      },
      {
        containerElements: [colorPickerPopperRef, colorPickerPopperButtonRef],
        name: 'TextStoryCreator.colorPicker',
      }
    );
  }, [isColorPickerShowing, colorPickerPopperRef, colorPickerPopperButtonRef]);

  const sliderColorNumber = getRGBANumber(sliderValue);

  let textForegroundColor = sliderColorNumber;
  let textBackgroundColor: number | undefined;

  if (textBackground === TextBackground.Background) {
    textBackgroundColor = COLOR_WHITE_INT;
    textForegroundColor =
      sliderValue >= 95 ? COLOR_BLACK_INT : sliderColorNumber;
  } else if (textBackground === TextBackground.Inverse) {
    textBackgroundColor =
      sliderValue >= 95 ? COLOR_BLACK_INT : sliderColorNumber;
    textForegroundColor = COLOR_WHITE_INT;
  }

  const textAttachment: TextAttachmentType = {
    ...getBackground(selectedBackground),
    text,
    textStyle,
    textForegroundColor,
    textBackgroundColor,
    preview: hasLinkPreviewApplied ? linkPreview : undefined,
  };

  const hasChanges = Boolean(text || hasLinkPreviewApplied);

  const textEditorRef = useRef<HTMLTextAreaElement | null>(null);

  return (
    <FocusTrap focusTrapOptions={{ allowOutsideClick: true }}>
      <div className="StoryCreator">
        <div className="StoryCreator__container">
          <TextAttachment
            disableLinkPreviewPopup
            i18n={i18n}
            isEditingText={isEditingText}
            onChange={setText}
            onClick={() => {
              if (!isEditingText) {
                setIsEditingText(true);
              }
            }}
            onRemoveLinkPreview={() => {
              setLinkPreviewApplied(LinkPreviewApplied.None);
            }}
            ref={textEditorRef}
            textAttachment={textAttachment}
          />
        </div>
        <div className="StoryCreator__toolbar">
          {isEditingText ? (
            <div className="StoryCreator__tools">
              <Slider
                handleStyle={{ backgroundColor: getRGBA(sliderValue) }}
                label={getRGBA(sliderValue)}
                moduleClassName="HueSlider StoryCreator__tools__tool"
                onChange={setSliderValue}
                value={sliderValue}
              />
              <ContextMenu
                i18n={i18n}
                menuOptions={[
                  {
                    icon: 'StoryCreator__icon--font-regular',
                    label: i18n('icu:StoryCreator__text--regular'),
                    onClick: () => setTextStyle(TextStyle.Regular),
                    value: TextStyle.Regular,
                  },
                  {
                    icon: 'StoryCreator__icon--font-bold',
                    label: i18n('icu:StoryCreator__text--bold'),
                    onClick: () => setTextStyle(TextStyle.Bold),
                    value: TextStyle.Bold,
                  },
                  {
                    icon: 'StoryCreator__icon--font-serif',
                    label: i18n('icu:StoryCreator__text--serif'),
                    onClick: () => setTextStyle(TextStyle.Serif),
                    value: TextStyle.Serif,
                  },
                  {
                    icon: 'StoryCreator__icon--font-script',
                    label: i18n('icu:StoryCreator__text--script'),
                    onClick: () => setTextStyle(TextStyle.Script),
                    value: TextStyle.Script,
                  },
                  {
                    icon: 'StoryCreator__icon--font-condensed',
                    label: i18n('icu:StoryCreator__text--condensed'),
                    onClick: () => setTextStyle(TextStyle.Condensed),
                    value: TextStyle.Condensed,
                  },
                ]}
                moduleClassName={classNames('StoryCreator__tools__tool', {
                  'StoryCreator__tools__button--font-regular':
                    textStyle === TextStyle.Regular,
                  'StoryCreator__tools__button--font-bold':
                    textStyle === TextStyle.Bold,
                  'StoryCreator__tools__button--font-serif':
                    textStyle === TextStyle.Serif,
                  'StoryCreator__tools__button--font-script':
                    textStyle === TextStyle.Script,
                  'StoryCreator__tools__button--font-condensed':
                    textStyle === TextStyle.Condensed,
                })}
                theme={Theme.Dark}
                value={textStyle}
              />
              <button
                aria-label={getBgButtonAriaLabel(i18n, textBackground)}
                className={classNames('StoryCreator__tools__tool', {
                  'StoryCreator__tools__button--bg-none':
                    textBackground === TextBackground.None,
                  'StoryCreator__tools__button--bg':
                    textBackground === TextBackground.Background,
                  'StoryCreator__tools__button--bg-inverse':
                    textBackground === TextBackground.Inverse,
                })}
                onClick={() => {
                  if (textBackground === TextBackground.None) {
                    setTextBackground(TextBackground.Background);
                  } else if (textBackground === TextBackground.Background) {
                    setTextBackground(TextBackground.Inverse);
                  } else {
                    setTextBackground(TextBackground.None);
                  }
                }}
                type="button"
              />
              <EmojiButton
                className="StoryCreator__emoji-button"
                i18n={i18n}
                onPickEmoji={data => {
                  onUseEmoji(data);
                  const emoji = convertShortName(data.shortName, data.skinTone);
                  const insertAt =
                    textEditorRef.current?.selectionEnd ?? text.length;
                  setText(
                    originalText =>
                      `${originalText.substr(
                        0,
                        insertAt
                      )}${emoji}${originalText.substr(insertAt, text.length)}`
                  );
                }}
                recentEmojis={recentEmojis}
                skinTone={skinTone}
                onSetSkinTone={onSetSkinTone}
              />
            </div>
          ) : (
            <div className="StoryCreator__toolbar--space" />
          )}
          <div className="StoryCreator__toolbar--buttons">
            <Button
              onClick={onTryClose}
              theme={Theme.Dark}
              variant={ButtonVariant.Secondary}
            >
              {i18n('icu:discard')}
            </Button>
            <div className="StoryCreator__controls">
              <button
                aria-label={i18n('icu:StoryCreator__story-bg')}
                className={classNames({
                  StoryCreator__control: true,
                  'StoryCreator__control--bg': true,
                  'StoryCreator__control--bg--selected': isColorPickerShowing,
                })}
                onClick={() => setIsColorPickerShowing(!isColorPickerShowing)}
                ref={setColorPickerPopperButtonRef}
                style={{
                  background: getBackgroundColor(
                    getBackground(selectedBackground)
                  ),
                }}
                type="button"
              />
              {isColorPickerShowing && (
                <div
                  className="StoryCreator__popper"
                  ref={setColorPickerPopperRef}
                  style={colorPickerPopper.styles.popper}
                  {...colorPickerPopper.attributes.popper}
                >
                  <div
                    data-popper-arrow
                    className="StoryCreator__popper__arrow"
                  />
                  {objectMap<BackgroundStyleType>(
                    BackgroundStyle,
                    (bg, backgroundValue) => (
                      <button
                        aria-label={i18n('icu:StoryCreator__story-bg')}
                        className={classNames({
                          StoryCreator__bg: true,
                          'StoryCreator__bg--selected':
                            selectedBackground === backgroundValue,
                        })}
                        key={String(bg)}
                        onClick={() => {
                          setSelectedBackground(backgroundValue);
                          setIsColorPickerShowing(false);
                        }}
                        type="button"
                        style={{
                          background: getBackgroundColor(
                            getBackground(backgroundValue)
                          ),
                        }}
                      />
                    )
                  )}
                </div>
              )}
              <button
                aria-label={i18n('icu:StoryCreator__control--text')}
                className={classNames({
                  StoryCreator__control: true,
                  'StoryCreator__control--text': true,
                  'StoryCreator__control--selected': isEditingText,
                })}
                onClick={() => {
                  setIsEditingText(!isEditingText);
                }}
                type="button"
              />
              <button
                aria-label={i18n('icu:StoryCreator__control--link')}
                className="StoryCreator__control StoryCreator__control--link"
                onClick={() =>
                  setIsLinkPreviewInputShowing(!isLinkPreviewInputShowing)
                }
                ref={setLinkPreviewInputPopperButtonRef}
                type="button"
              />
              {isLinkPreviewInputShowing && (
                <div
                  className={classNames(
                    'StoryCreator__popper StoryCreator__link-preview-input-popper',
                    themeClassName(Theme.Dark)
                  )}
                  ref={setLinkPreviewInputPopperRef}
                  style={linkPreviewInputPopper.styles.popper}
                  {...linkPreviewInputPopper.attributes.popper}
                >
                  <div
                    data-popper-arrow
                    className="StoryCreator__popper__arrow"
                  />
                  <Input
                    disableSpellcheck
                    i18n={i18n}
                    moduleClassName="StoryCreator__link-preview-input"
                    onChange={setLinkPreviewInputValue}
                    placeholder={i18n(
                      'icu:StoryCreator__link-preview-placeholder'
                    )}
                    ref={el => el?.focus()}
                    value={linkPreviewInputValue}
                  />
                  <div className="StoryCreator__link-preview-container">
                    {linkPreview ? (
                      <>
                        <div className="StoryCreator__link-preview-wrapper">
                          <StoryLinkPreview
                            {...linkPreview}
                            forceCompactMode
                            i18n={i18n}
                          />
                        </div>
                        <Button
                          className="StoryCreator__link-preview-button"
                          onClick={() => {
                            setLinkPreviewApplied(LinkPreviewApplied.Manual);
                            setIsLinkPreviewInputShowing(false);
                          }}
                          theme={Theme.Dark}
                          variant={ButtonVariant.Primary}
                        >
                          {i18n('icu:StoryCreator__add-link')}
                        </Button>
                      </>
                    ) : (
                      <div className="StoryCreator__link-preview-empty">
                        <div className="StoryCreator__link-preview-empty__icon" />
                        {i18n('icu:StoryCreator__link-preview-empty')}
                      </div>
                    )}
                  </div>
                </div>
              )}
            </div>
            <Button
              disabled={!hasChanges || isSending}
              onClick={() => onDone(textAttachment)}
              theme={Theme.Dark}
              variant={ButtonVariant.Primary}
            >
              {isSending ? (
                <Spinner svgSize="small" />
              ) : (
                i18n('icu:StoryCreator__next')
              )}
            </Button>
          </div>
        </div>
        {showConfirmDiscardModal && (
          <ConfirmDiscardDialog
            i18n={i18n}
            onClose={() => setShowConfirmDiscardModal(false)}
            onDiscard={onClose}
          />
        )}
      </div>
    </FocusTrap>
  );
}