import type { FC } from '../../../lib/teact/teact';
import React, {
  memo, useEffect, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';

import type {
  ApiFormattedText, ApiMessage, ApiMessageEntityTextUrl, ApiWebPage,
} from '../../../api/types';
import type { GlobalState } from '../../../global/types';
import type { ISettings, ThreadId } from '../../../types';
import type { Signal } from '../../../util/signals';
import { ApiMessageEntityTypes } from '../../../api/types';

import { RE_LINK_TEMPLATE } from '../../../config';
import { selectNoWebPage, selectTabState, selectTheme } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';

import { useDebouncedResolver } from '../../../hooks/useAsyncResolvers';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useDerivedSignal from '../../../hooks/useDerivedSignal';
import useDerivedState from '../../../hooks/useDerivedState';
import useLastCallback from '../../../hooks/useLastCallback';
import useMenuPosition from '../../../hooks/useMenuPosition';
import useOldLang from '../../../hooks/useOldLang';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
import useSyncEffect from '../../../hooks/useSyncEffect';

import Button from '../../ui/Button';
import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem';
import WebPage from '../message/WebPage';

import './WebPagePreview.scss';

type OwnProps = {
  chatId: string;
  threadId: ThreadId;
  getHtml: Signal<string>;
  isEditing: boolean;
  isDisabled?: boolean;
};

type StateProps = {
  webPagePreview?: ApiWebPage;
  noWebPage?: boolean;
  theme: ISettings['theme'];
  attachmentSettings: GlobalState['attachmentSettings'];
};

const DEBOUNCE_MS = 300;
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');

const WebPagePreview: FC<OwnProps & StateProps> = ({
  chatId,
  threadId,
  getHtml,
  isDisabled,
  webPagePreview,
  noWebPage,
  theme,
  attachmentSettings,
  isEditing,
}) => {
  const {
    loadWebPagePreview,
    clearWebPagePreview,
    toggleMessageWebPage,
    updateAttachmentSettings,
  } = getActions();

  const lang = useOldLang();

  const formattedTextWithLinkRef = useRef<ApiFormattedText>();

  // eslint-disable-next-line no-null/no-null
  const ref = useRef<HTMLDivElement>(null);

  const isInvertedMedia = attachmentSettings.isInvertedMedia;

  const detectLinkDebounced = useDebouncedResolver(() => {
    const formattedText = parseHtmlAsFormattedText(getHtml());
    const linkEntity = formattedText.entities?.find((entity): entity is ApiMessageEntityTextUrl => (
      entity.type === ApiMessageEntityTypes.TextUrl
    ));

    formattedTextWithLinkRef.current = formattedText;

    return linkEntity?.url || formattedText.text.match(RE_LINK)?.[0];
  }, [getHtml], DEBOUNCE_MS, true);

  const getLink = useDerivedSignal(detectLinkDebounced, [detectLinkDebounced, getHtml], true);

  useEffect(() => {
    const link = getLink();
    const formattedText = formattedTextWithLinkRef.current;

    if (link) {
      loadWebPagePreview({ text: formattedText! });
    } else {
      clearWebPagePreview();
      toggleMessageWebPage({ chatId, threadId });
    }
  }, [getLink, chatId, threadId]);

  useSyncEffect(() => {
    clearWebPagePreview();
    toggleMessageWebPage({ chatId, threadId });
  }, [chatId, clearWebPagePreview, threadId, toggleMessageWebPage]);

  const isShown = useDerivedState(() => {
    return Boolean(webPagePreview && getHtml() && !noWebPage && !isDisabled);
  }, [isDisabled, getHtml, noWebPage, webPagePreview]);
  const { shouldRender, transitionClassNames } = useShowTransitionDeprecated(isShown);

  const renderingWebPage = useCurrentOrPrev(webPagePreview, true);

  const handleClearWebpagePreview = useLastCallback(() => {
    toggleMessageWebPage({ chatId, threadId, noWebPage: true });
  });

  const {
    isContextMenuOpen, contextMenuPosition, handleContextMenu,
    handleContextMenuClose, handleContextMenuHide,
  } = useContextMenuHandlers(ref, isEditing, true);

  const getTriggerElement = useLastCallback(() => ref.current);
  const getRootElement = useLastCallback(() => ref.current!);
  const getMenuElement = useLastCallback(
    () => ref.current!.querySelector('.web-page-preview-context-menu .bubble'),
  );

  const {
    positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
  } = useMenuPosition(
    contextMenuPosition,
    getTriggerElement,
    getRootElement,
    getMenuElement,
  );

  const handlePreviewClick = useLastCallback((e: React.MouseEvent): void => {
    handleContextMenu(e);
  });

  useEffect(() => {
    if (!shouldRender || !renderingWebPage) {
      handleContextMenuClose();
      handleContextMenuHide();
    }
  }, [handleContextMenuClose, handleContextMenuHide, shouldRender, renderingWebPage]);

  function updateIsInvertedMedia(value?: true) {
    updateAttachmentSettings({ isInvertedMedia: value });
  }

  if (!shouldRender || !renderingWebPage) {
    return undefined;
  }

  // TODO Refactor so `WebPage` can be used without message
  const { photo, ...webPageWithoutPhoto } = renderingWebPage;
  const messageStub = {
    content: {
      webPage: webPageWithoutPhoto,
    },
  } as ApiMessage;

  function renderContextMenu() {
    return (
      <Menu
        isOpen={isContextMenuOpen}
        transformOriginX={transformOriginX}
        transformOriginY={transformOriginY}
        positionX={positionX}
        positionY={positionY}
        style={menuStyle}
        className="web-page-preview-context-menu"
        onClose={handleContextMenuClose}
        onCloseAnimationEnd={handleContextMenuHide}
        autoClose
      >
        <>
          {
            isInvertedMedia ? (
            // eslint-disable-next-line react/jsx-no-bind
              <MenuItem icon="move-caption-up" onClick={() => updateIsInvertedMedia(undefined)}>
                {lang('PreviewSender.MoveTextUp')}
              </MenuItem>
            ) : (
            // eslint-disable-next-line react/jsx-no-bind
              <MenuItem icon="move-caption-down" onClick={() => updateIsInvertedMedia(true)}>
                {lang(('PreviewSender.MoveTextDown'))}
              </MenuItem>
            )
          }
          <MenuItem
            icon="delete"
            // eslint-disable-next-line react/jsx-no-bind
            onClick={handleClearWebpagePreview}
          >
            {lang('ChatInput.EditLink.RemovePreview')}
          </MenuItem>
        </>
      </Menu>
    );
  }

  return (
    <div className={buildClassName('WebPagePreview', transitionClassNames)} ref={ref}>
      <div className="WebPagePreview_inner">
        <div className="WebPagePreview-left-icon" onClick={handlePreviewClick}>
          <i className="icon icon-link" />
        </div>
        <WebPage
          message={messageStub}
          inPreview
          theme={theme}
          onContainerClick={handlePreviewClick}
          isEditing={isEditing}
        />
        <Button
          className="WebPagePreview-clear"
          round
          faded
          color="translucent"
          ariaLabel="Clear Webpage Preview"
          onClick={handleClearWebpagePreview}
        >
          <i className="icon icon-close" />
        </Button>
        {!isEditing && renderContextMenu()}
      </div>
    </div>
  );
};

export default memo(withGlobal<OwnProps>(
  (global, { chatId, threadId }): StateProps => {
    const noWebPage = selectNoWebPage(global, chatId, threadId);
    const {
      attachmentSettings,
    } = global;
    return {
      theme: selectTheme(global),
      webPagePreview: selectTabState(global).webPagePreview,
      noWebPage,
      attachmentSettings,
    };
  },
)(WebPagePreview));
