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

import * as React from 'react';
import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';

import { useRestoreFocus } from '../../hooks/useRestoreFocus';
import type { StickerPackType, StickerType } from '../../state/ducks/stickers';
import type { LocalizerType } from '../../types/Util';
import { getAnalogTime } from '../../util/getAnalogTime';
import { getDateTimeFormatter } from '../../util/formatTimestamp';

export type OwnProps = {
  readonly i18n: LocalizerType;
  readonly onClose: () => unknown;
  readonly onClickAddPack?: () => unknown;
  readonly onPickSticker: (
    packId: string,
    stickerId: number,
    url: string
  ) => unknown;
  readonly onPickTimeSticker?: (style: 'analog' | 'digital') => unknown;
  readonly packs: ReadonlyArray<StickerPackType>;
  readonly recentStickers: ReadonlyArray<StickerType>;
  readonly showPickerHint?: boolean;
};

export type Props = OwnProps & Pick<React.HTMLProps<HTMLDivElement>, 'style'>;

function useTabs<T>(tabs: ReadonlyArray<T>, initialTab = tabs[0]) {
  const [tab, setTab] = React.useState(initialTab);
  const handlers = React.useMemo(
    () =>
      tabs.map(t => () => {
        setTab(t);
      }),
    [tabs]
  );

  return [tab, handlers] as [T, ReadonlyArray<() => void>];
}

const PACKS_PAGE_SIZE = 7;
const PACK_ICON_WIDTH = 32;
const PACK_PAGE_WIDTH = PACKS_PAGE_SIZE * PACK_ICON_WIDTH;

function getPacksPageOffset(page: number, packs: number): number {
  if (page === 0) {
    return 0;
  }

  if (isLastPacksPage(page, packs)) {
    return (
      PACK_PAGE_WIDTH * (Math.floor(packs / PACKS_PAGE_SIZE) - 1) +
      ((packs % PACKS_PAGE_SIZE) - 1) * PACK_ICON_WIDTH
    );
  }

  return page * PACK_ICON_WIDTH * PACKS_PAGE_SIZE;
}

function isLastPacksPage(page: number, packs: number): boolean {
  return page === Math.floor(packs / PACKS_PAGE_SIZE);
}

export const StickerPicker = React.memo(
  React.forwardRef<HTMLDivElement, Props>(
    (
      {
        i18n,
        packs,
        recentStickers,
        onClose,
        onClickAddPack,
        onPickSticker,
        onPickTimeSticker,
        showPickerHint,
        style,
      }: Props,
      ref
    ) => {
      const tabIds = React.useMemo(
        () => ['recents', ...packs.map(({ id }) => id)],
        [packs]
      );
      const [currentTab, [recentsHandler, ...packsHandlers]] = useTabs(
        tabIds,
        // If there are no recent stickers,
        // default to the first sticker pack,
        // unless there are no sticker packs.
        tabIds[recentStickers.length > 0 ? 0 : Math.min(1, tabIds.length)]
      );
      const selectedPack = packs.find(({ id }) => id === currentTab);
      const {
        stickers = recentStickers,
        title: packTitle = 'Recent Stickers',
      } = selectedPack || {};

      const [isUsingKeyboard, setIsUsingKeyboard] = React.useState(false);
      const [packsPage, setPacksPage] = React.useState(0);
      const onClickPrevPackPage = React.useCallback(() => {
        setPacksPage(i => i - 1);
      }, [setPacksPage]);
      const onClickNextPackPage = React.useCallback(() => {
        setPacksPage(i => i + 1);
      }, [setPacksPage]);

      // Handle escape key
      React.useEffect(() => {
        const handler = (event: KeyboardEvent) => {
          if (event.key === 'Tab') {
            // We do NOT prevent default here to allow Tab to be used normally

            setIsUsingKeyboard(true);

            return;
          }

          if (event.key === 'Escape') {
            event.stopPropagation();
            event.preventDefault();

            onClose();
          }
        };

        document.addEventListener('keydown', handler);

        return () => {
          document.removeEventListener('keydown', handler);
        };
      }, [onClose]);

      // Focus popup on after initial render, restore focus on teardown
      const [focusRef] = useRestoreFocus();

      const hasPacks = packs.length > 0;
      const isRecents = hasPacks && currentTab === 'recents';
      const hasTimeStickers = isRecents && onPickTimeSticker;
      const isEmpty = stickers.length === 0 && !hasTimeStickers;
      const addPackRef = isEmpty ? focusRef : undefined;
      const downloadError =
        selectedPack &&
        selectedPack.status === 'error' &&
        selectedPack.stickerCount !== selectedPack.stickers.length;
      const pendingCount =
        selectedPack && selectedPack.status === 'pending'
          ? selectedPack.stickerCount - stickers.length
          : 0;

      const showPendingText = pendingCount > 0;
      const showDownloadErrorText = downloadError;
      const showEmptyText = !downloadError && isEmpty;
      const showText =
        showPendingText || showDownloadErrorText || showEmptyText;
      const showLongText = showPickerHint;
      const analogTime = getAnalogTime();

      return (
        <FocusTrap
          focusTrapOptions={{
            allowOutsideClick: true,
          }}
        >
          <div className="module-sticker-picker" ref={ref} style={style}>
            <div className="module-sticker-picker__header">
              <div className="module-sticker-picker__header__packs">
                <div
                  className="module-sticker-picker__header__packs__slider"
                  style={{
                    transform: `translateX(-${getPacksPageOffset(
                      packsPage,
                      packs.length
                    )}px)`,
                  }}
                >
                  {hasPacks ? (
                    <button
                      aria-pressed={currentTab === 'recents'}
                      type="button"
                      onClick={recentsHandler}
                      className={classNames({
                        'module-sticker-picker__header__button': true,
                        'module-sticker-picker__header__button--recents': true,
                        'module-sticker-picker__header__button--selected':
                          currentTab === 'recents',
                      })}
                      aria-label={i18n('icu:stickers--StickerPicker--Recents')}
                    />
                  ) : null}
                  {packs.map((pack, i) => (
                    <button
                      aria-pressed={currentTab === pack.id}
                      type="button"
                      key={pack.id}
                      onClick={packsHandlers[i]}
                      className={classNames(
                        'module-sticker-picker__header__button',
                        {
                          'module-sticker-picker__header__button--selected':
                            currentTab === pack.id,
                          'module-sticker-picker__header__button--error':
                            pack.status === 'error',
                        }
                      )}
                    >
                      {pack.cover ? (
                        <img
                          className="module-sticker-picker__header__button__image"
                          src={pack.cover.url}
                          alt={pack.title}
                          title={pack.title}
                        />
                      ) : (
                        <div className="module-sticker-picker__header__button__image-placeholder" />
                      )}
                    </button>
                  ))}
                </div>
                {!isUsingKeyboard && packsPage > 0 ? (
                  <button
                    type="button"
                    className={classNames(
                      'module-sticker-picker__header__button',
                      'module-sticker-picker__header__button--prev-page'
                    )}
                    onClick={onClickPrevPackPage}
                    aria-label={i18n('icu:stickers--StickerPicker--PrevPage')}
                  />
                ) : null}
                {!isUsingKeyboard &&
                !isLastPacksPage(packsPage, packs.length) ? (
                  <button
                    type="button"
                    className={classNames(
                      'module-sticker-picker__header__button',
                      'module-sticker-picker__header__button--next-page'
                    )}
                    onClick={onClickNextPackPage}
                    aria-label={i18n('icu:stickers--StickerPicker--NextPage')}
                  />
                ) : null}
              </div>
              {onClickAddPack && (
                <button
                  type="button"
                  ref={addPackRef}
                  className={classNames(
                    'module-sticker-picker__header__button',
                    'module-sticker-picker__header__button--add-pack',
                    {
                      'module-sticker-picker__header__button--hint':
                        showPickerHint,
                    }
                  )}
                  onClick={onClickAddPack}
                  aria-label={i18n('icu:stickers--StickerPicker--AddPack')}
                />
              )}
            </div>
            <div
              className={classNames('module-sticker-picker__body', {
                'module-sticker-picker__body--empty': isEmpty,
              })}
            >
              {showPickerHint ? (
                <div
                  className={classNames(
                    'module-sticker-picker__body__text',
                    'module-sticker-picker__body__text--hint',
                    {
                      'module-sticker-picker__body__text--pin': showEmptyText,
                    }
                  )}
                >
                  {i18n('icu:stickers--StickerPicker--Hint')}
                </div>
              ) : null}
              {!hasPacks ? (
                <div className="module-sticker-picker__body__text">
                  {i18n('icu:stickers--StickerPicker--NoPacks')}
                </div>
              ) : null}
              {pendingCount > 0 ? (
                <div className="module-sticker-picker__body__text">
                  {i18n('icu:stickers--StickerPicker--DownloadPending')}
                </div>
              ) : null}
              {downloadError ? (
                <div
                  className={classNames(
                    'module-sticker-picker__body__text',
                    'module-sticker-picker__body__text--error'
                  )}
                >
                  {stickers.length > 0
                    ? i18n('icu:stickers--StickerPicker--DownloadError')
                    : i18n('icu:stickers--StickerPicker--Empty')}
                </div>
              ) : null}
              {hasPacks && showEmptyText ? (
                <div
                  className={classNames('module-sticker-picker__body__text', {
                    'module-sticker-picker__body__text--error': !isRecents,
                  })}
                >
                  {isRecents
                    ? i18n('icu:stickers--StickerPicker--NoRecents')
                    : i18n('icu:stickers--StickerPicker--Empty')}
                </div>
              ) : null}
              {!isEmpty ? (
                <div className="module-sticker-picker__body__content">
                  {isRecents && onPickTimeSticker && (
                    <div className="module-sticker-picker__recents">
                      <strong className="module-sticker-picker__recents__title">
                        {i18n('icu:stickers__StickerPicker__featured')}
                      </strong>
                      <div className="module-sticker-picker__body__grid">
                        <button
                          type="button"
                          className="module-sticker-picker__body__cell module-sticker-picker__time--digital"
                          onClick={() => onPickTimeSticker('digital')}
                        >
                          {getDateTimeFormatter({
                            hour: 'numeric',
                            minute: 'numeric',
                          })
                            .formatToParts(Date.now())
                            .filter(x => x.type !== 'dayPeriod')
                            .reduce((acc, { value }) => `${acc}${value}`, '')}
                        </button>

                        <button
                          aria-label={i18n(
                            'icu:stickers__StickerPicker__analog-time'
                          )}
                          className="module-sticker-picker__body__cell module-sticker-picker__time--analog"
                          onClick={() => onPickTimeSticker('analog')}
                          type="button"
                        >
                          <span
                            className="module-sticker-picker__time--analog__hour"
                            style={{
                              transform: `rotate(${analogTime.hour}deg)`,
                            }}
                          />
                          <span
                            className="module-sticker-picker__time--analog__minute"
                            style={{
                              transform: `rotate(${analogTime.minute}deg)`,
                            }}
                          />
                        </button>
                      </div>
                      {stickers.length > 0 && (
                        <strong className="module-sticker-picker__recents__title">
                          {i18n('icu:stickers__StickerPicker__recent')}
                        </strong>
                      )}
                    </div>
                  )}
                  <div
                    className={classNames('module-sticker-picker__body__grid', {
                      'module-sticker-picker__body__content--under-text':
                        showText,
                      'module-sticker-picker__body__content--under-long-text':
                        showLongText,
                    })}
                  >
                    {stickers.map(({ packId, id, url }, index: number) => {
                      const maybeFocusRef = index === 0 ? focusRef : undefined;

                      return (
                        <button
                          type="button"
                          ref={maybeFocusRef}
                          key={`${packId}-${id}`}
                          className="module-sticker-picker__body__cell"
                          onClick={() => onPickSticker(packId, id, url)}
                        >
                          <img
                            className="module-sticker-picker__body__cell__image"
                            src={url}
                            alt={packTitle}
                          />
                        </button>
                      );
                    })}
                    {Array(pendingCount)
                      .fill(0)
                      .map((_, i) => (
                        <div
                          // eslint-disable-next-line react/no-array-index-key
                          key={i}
                          className="module-sticker-picker__body__cell__placeholder"
                          role="presentation"
                        />
                      ))}
                  </div>
                </div>
              ) : null}
            </div>
          </div>
        </FocusTrap>
      );
    }
  )
);