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

import type { KeyboardEvent, MouseEvent } from 'react';
import React, { useRef, useState } from 'react';
import classNames from 'classnames';
import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu';
import { ConfirmationDialog } from './ConfirmationDialog';
import { CustomColorEditor } from './CustomColorEditor';
import { Modal } from './Modal';
import type { ConversationColorType, CustomColorType } from '../types/Colors';
import { ConversationColors } from '../types/Colors';
import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util';
import { SampleMessageBubbles } from './SampleMessageBubbles';
import { PanelRow } from './conversation/conversation-details/PanelRow';
import { getCustomColorStyle } from '../util/getCustomColorStyle';

import { useDelayedRestoreFocus } from '../hooks/useRestoreFocus';

type CustomColorDataType = {
  id?: string;
  value?: CustomColorType;
};

export type PropsDataType = {
  conversationId?: string;
  customColors?: Record<string, CustomColorType>;
  getConversationsWithCustomColor: (
    colorId: string
  ) => Promise<Array<ConversationType>>;
  i18n: LocalizerType;
  isGlobal?: boolean;
  selectedColor?: ConversationColorType;
  selectedCustomColor: CustomColorDataType;
};

type PropsActionType = {
  addCustomColor: (color: CustomColorType, conversationId?: string) => unknown;
  colorSelected: (payload: {
    conversationId: string;
    conversationColor?: ConversationColorType;
    customColorData?: {
      id: string;
      value: CustomColorType;
    };
  }) => unknown;
  editCustomColor: (colorId: string, color: CustomColorType) => unknown;
  removeCustomColor: (colorId: string) => unknown;
  removeCustomColorOnConversations: (colorId: string) => unknown;
  resetAllChatColors: () => unknown;
  resetDefaultChatColor: () => unknown;
  setGlobalDefaultConversationColor: (
    color: ConversationColorType,
    customColorData?: {
      id: string;
      value: CustomColorType;
    }
  ) => unknown;
};

export type PropsType = PropsDataType & PropsActionType;

export function ChatColorPicker({
  addCustomColor,
  colorSelected,
  conversationId,
  customColors = {},
  editCustomColor,
  getConversationsWithCustomColor,
  i18n,
  isGlobal = false,
  removeCustomColor,
  removeCustomColorOnConversations,
  resetAllChatColors,
  resetDefaultChatColor,
  selectedColor = ConversationColors[0],
  selectedCustomColor,
  setGlobalDefaultConversationColor,
}: PropsType): JSX.Element {
  const [confirmResetAll, setConfirmResetAll] = useState(false);
  const [confirmResetWhat, setConfirmResetWhat] = useState(false);
  const [customColorToEdit, setCustomColorToEdit] = useState<
    CustomColorDataType | undefined
  >(undefined);

  const [focusRef] = useDelayedRestoreFocus();

  const onSelectColor = (
    conversationColor: ConversationColorType,
    customColorData?: { id: string; value: CustomColorType }
  ): void => {
    if (conversationId) {
      colorSelected({
        conversationId,
        conversationColor,
        customColorData,
      });
    } else {
      setGlobalDefaultConversationColor(conversationColor, customColorData);
    }
  };

  const renderCustomColorEditorWrapper = () => (
    <CustomColorEditorWrapper
      customColorToEdit={customColorToEdit}
      i18n={i18n}
      onClose={() => setCustomColorToEdit(undefined)}
      onSave={(color: CustomColorType) => {
        if (customColorToEdit?.id) {
          editCustomColor(customColorToEdit.id, color);
          onSelectColor('custom', {
            id: customColorToEdit.id,
            value: color,
          });
        } else {
          addCustomColor(color, conversationId);
        }
      }}
    />
  );

  return (
    <div className="ChatColorPicker__container">
      {customColorToEdit ? renderCustomColorEditorWrapper() : null}
      {confirmResetWhat ? (
        <ConfirmationDialog
          dialogName="ChatColorPicker.confirmReset"
          actions={[
            {
              action: resetDefaultChatColor,
              style: 'affirmative',
              text: i18n('icu:ChatColorPicker__confirm-reset-default'),
            },
            {
              action: () => {
                resetDefaultChatColor();
                resetAllChatColors();
              },
              style: 'affirmative',
              text: i18n('icu:ChatColorPicker__resetAll'),
            },
          ]}
          i18n={i18n}
          onClose={() => {
            setConfirmResetWhat(false);
          }}
          title={i18n('icu:ChatColorPicker__resetDefault')}
        >
          {i18n('icu:ChatColorPicker__confirm-reset-message')}
        </ConfirmationDialog>
      ) : null}
      {confirmResetAll ? (
        <ConfirmationDialog
          dialogName="ChatColorPicker.confirmResetAll"
          actions={[
            {
              action: resetAllChatColors,
              style: 'affirmative',
              text: i18n('icu:ChatColorPicker__confirm-reset'),
            },
          ]}
          i18n={i18n}
          onClose={() => {
            setConfirmResetAll(false);
          }}
          title={i18n('icu:ChatColorPicker__resetAll')}
        >
          {i18n('icu:ChatColorPicker__confirm-reset-message')}
        </ConfirmationDialog>
      ) : null}
      <SampleMessageBubbles
        backgroundStyle={getCustomColorStyle(selectedCustomColor.value)}
        color={selectedColor}
        i18n={i18n}
      />
      <hr />
      <div className="ChatColorPicker__bubbles">
        {ConversationColors.map((color, i) => (
          <div
            aria-label={color}
            aria-selected={color === selectedColor}
            className={classNames(
              `ChatColorPicker__bubble ChatColorPicker__bubble--${color}`,
              {
                'ChatColorPicker__bubble--selected': color === selectedColor,
              }
            )}
            key={color}
            onClick={() => onSelectColor(color)}
            onKeyDown={(ev: KeyboardEvent) => {
              if (ev.key === 'Enter') {
                onSelectColor(color);
              }
            }}
            role="option"
            tabIndex={0}
            ref={i === 0 ? focusRef : undefined}
          />
        ))}
        {Object.keys(customColors).map(colorId => {
          const colorValues = customColors[colorId];
          return (
            <CustomColorBubble
              color={colorValues}
              colorId={colorId}
              getConversationsWithCustomColor={getConversationsWithCustomColor}
              key={colorId}
              i18n={i18n}
              isSelected={colorId === selectedCustomColor.id}
              onChoose={() => {
                onSelectColor('custom', {
                  id: colorId,
                  value: colorValues,
                });
              }}
              onDelete={() => {
                removeCustomColor(colorId);
                removeCustomColorOnConversations(colorId);
              }}
              onDupe={() => {
                addCustomColor(colorValues, conversationId);
              }}
              onEdit={() => {
                setCustomColorToEdit({ id: colorId, value: colorValues });
              }}
            />
          );
        })}
        <div
          aria-label={i18n('icu:ChatColorPicker__custom-color--label')}
          className="ChatColorPicker__bubble ChatColorPicker__bubble--custom"
          onClick={() =>
            setCustomColorToEdit({ id: undefined, value: undefined })
          }
          onKeyDown={(ev: KeyboardEvent) => {
            if (ev.key === 'Enter') {
              setCustomColorToEdit({ id: undefined, value: undefined });
            }
          }}
          role="button"
          tabIndex={0}
        >
          <i className="ChatColorPicker__add-icon" />
        </div>
      </div>
      <hr />
      {conversationId ? (
        <PanelRow
          label={i18n('icu:ChatColorPicker__reset')}
          onClick={() => {
            colorSelected({ conversationId });
          }}
        />
      ) : null}
      <PanelRow
        label={i18n('icu:ChatColorPicker__resetAll')}
        onClick={() => {
          if (isGlobal) {
            setConfirmResetWhat(true);
          } else {
            setConfirmResetAll(true);
          }
        }}
      />
    </div>
  );
}

type CustomColorBubblePropsType = {
  color: CustomColorType;
  colorId: string;
  getConversationsWithCustomColor: (
    colorId: string
  ) => Promise<Array<ConversationType>>;
  i18n: LocalizerType;
  isSelected: boolean;
  onDelete: () => unknown;
  onDupe: () => unknown;
  onEdit: () => unknown;
  onChoose: () => unknown;
};

function CustomColorBubble({
  color,
  colorId,
  getConversationsWithCustomColor,
  i18n,
  isSelected,
  onDelete,
  onDupe,
  onEdit,
  onChoose,
}: CustomColorBubblePropsType): JSX.Element {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const menuRef = useRef<any | null>(null);
  const [confirmDeleteCount, setConfirmDeleteCount] = useState<
    number | undefined
  >(undefined);

  const handleClick = (ev: KeyboardEvent | MouseEvent) => {
    if (!isSelected) {
      onChoose();
      return;
    }

    if (menuRef && menuRef.current) {
      menuRef.current.handleContextClick(ev);
    }
  };

  const bubble = (
    <div
      aria-label={colorId}
      aria-selected={isSelected}
      className={classNames({
        ChatColorPicker__bubble: true,
        'ChatColorPicker__bubble--custom-selected': isSelected,
        'ChatColorPicker__bubble--selected': isSelected,
      })}
      onClick={handleClick}
      onKeyDown={(ev: KeyboardEvent) => {
        if (ev.key === 'Enter') {
          handleClick(ev);
        }
      }}
      role="option"
      tabIndex={0}
      style={{
        ...getCustomColorStyle(color),
      }}
    />
  );

  return (
    <>
      {confirmDeleteCount ? (
        <ConfirmationDialog
          dialogName="ChatColorPicker.confirmDelete"
          actions={[
            {
              action: onDelete,
              style: 'negative',
              text: i18n('icu:ChatColorPicker__context--delete'),
            },
          ]}
          i18n={i18n}
          onClose={() => {
            setConfirmDeleteCount(undefined);
          }}
          title={i18n('icu:ChatColorPicker__delete--title')}
        >
          {i18n('icu:ChatColorPicker__delete--message', {
            num: confirmDeleteCount,
          })}
        </ConfirmationDialog>
      ) : null}
      {isSelected ? (
        <ContextMenuTrigger id={colorId} ref={menuRef}>
          {bubble}
        </ContextMenuTrigger>
      ) : (
        bubble
      )}
      <ContextMenu id={colorId}>
        <MenuItem
          attributes={{
            className: 'ChatColorPicker__context--edit',
          }}
          onClick={(event: MouseEvent) => {
            event.stopPropagation();
            event.preventDefault();

            onEdit();
          }}
        >
          {i18n('icu:ChatColorPicker__context--edit')}
        </MenuItem>
        <MenuItem
          attributes={{
            className: 'ChatColorPicker__context--duplicate',
          }}
          onClick={(event: MouseEvent) => {
            event.stopPropagation();
            event.preventDefault();

            onDupe();
          }}
        >
          {i18n('icu:ChatColorPicker__context--duplicate')}
        </MenuItem>
        <MenuItem
          attributes={{
            className: 'ChatColorPicker__context--delete',
          }}
          onClick={async (event: MouseEvent) => {
            event.stopPropagation();
            event.preventDefault();

            const conversations = await getConversationsWithCustomColor(
              colorId
            );
            if (!conversations.length) {
              onDelete();
            } else {
              setConfirmDeleteCount(conversations.length);
            }
          }}
        >
          {i18n('icu:ChatColorPicker__context--delete')}
        </MenuItem>
      </ContextMenu>
    </>
  );
}

type CustomColorEditorWrapperPropsType = {
  customColorToEdit?: CustomColorDataType;
  i18n: LocalizerType;
  onClose: () => unknown;
  onSave: (color: CustomColorType) => unknown;
};

function CustomColorEditorWrapper({
  customColorToEdit,
  i18n,
  onClose,
  onSave,
}: CustomColorEditorWrapperPropsType): JSX.Element {
  const editor = (
    <CustomColorEditor
      customColor={customColorToEdit?.value}
      i18n={i18n}
      onClose={onClose}
      onSave={onSave}
    />
  );

  return (
    <Modal
      modalName="ChatColorPicker"
      hasXButton
      i18n={i18n}
      moduleClassName="ChatColorPicker__modal"
      noMouseClose
      onClose={onClose}
      title={i18n('icu:CustomColorEditor__title')}
    >
      {editor}
    </Modal>
  );
}