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

import type { ReactElement, ReactNode } from 'react';
import React, { useState } from 'react';
import { get } from 'lodash';

import * as log from '../../logging/log';
import type { ReplacementValuesType } from '../../types/I18N';
import type { FullJSXType } from '../Intl';
import { Intl } from '../Intl';
import type { LocalizerType } from '../../types/Util';
import type { UUIDStringType } from '../../types/UUID';
import { GroupDescriptionText } from '../GroupDescriptionText';
import { Button, ButtonSize, ButtonVariant } from '../Button';
import { SystemMessage } from './SystemMessage';

import type { GroupV2ChangeType, GroupV2ChangeDetailType } from '../../groups';

import type { SmartContactRendererType } from '../../groupChange';
import { renderChange } from '../../groupChange';
import { Modal } from '../Modal';
import { ConfirmationDialog } from '../ConfirmationDialog';

export type PropsDataType = {
  areWeAdmin: boolean;
  conversationId: string;
  groupMemberships?: ReadonlyArray<{
    uuid: UUIDStringType;
    isAdmin: boolean;
  }>;
  groupBannedMemberships?: ReadonlyArray<UUIDStringType>;
  groupName?: string;
  ourACI?: UUIDStringType;
  ourPNI?: UUIDStringType;
  change: GroupV2ChangeType;
};

export type PropsActionsType = {
  blockGroupLinkRequests: (
    conversationId: string,
    uuid: UUIDStringType
  ) => unknown;
};

export type PropsHousekeepingType = {
  i18n: LocalizerType;
  renderContact: SmartContactRendererType<FullJSXType>;
};

export type PropsType = PropsDataType &
  PropsActionsType &
  PropsHousekeepingType;

function renderStringToIntl(
  id: string,
  i18n: LocalizerType,
  components?: Array<FullJSXType> | ReplacementValuesType<FullJSXType>
): FullJSXType {
  return <Intl id={id} i18n={i18n} components={components} />;
}

enum ModalState {
  None = 'None',
  ViewingGroupDescription = 'ViewingGroupDescription',
  ConfirmingblockGroupLinkRequests = 'ConfirmingblockGroupLinkRequests',
}

type GroupIconType =
  | 'group'
  | 'group-access'
  | 'group-add'
  | 'group-approved'
  | 'group-avatar'
  | 'group-decline'
  | 'group-edit'
  | 'group-summary'
  | 'group-leave'
  | 'group-remove';

const changeToIconMap = new Map<string, GroupIconType>([
  ['access-attributes', 'group-access'],
  ['access-invite-link', 'group-access'],
  ['access-members', 'group-access'],
  ['admin-approval-add-one', 'group-add'],
  ['admin-approval-remove-one', 'group-decline'],
  ['admin-approval-bounce', 'group-decline'],
  ['announcements-only', 'group-access'],
  ['avatar', 'group-avatar'],
  ['description', 'group-edit'],
  ['group-link-add', 'group-access'],
  ['group-link-remove', 'group-access'],
  ['group-link-reset', 'group-access'],
  ['member-add', 'group-add'],
  ['member-add-from-admin-approval', 'group-approved'],
  ['member-add-from-invite', 'group-add'],
  ['member-add-from-link', 'group-add'],
  ['member-privilege', 'group-access'],
  ['member-remove', 'group-remove'],
  ['pending-add-many', 'group-add'],
  ['pending-add-one', 'group-add'],
  ['pending-remove-many', 'group-decline'],
  ['pending-remove-one', 'group-decline'],
  ['title', 'group-edit'],
]);

function getIcon(
  detail: GroupV2ChangeDetailType,
  isLastText = true,
  fromId?: UUIDStringType
): GroupIconType {
  const changeType = detail.type;
  let possibleIcon = changeToIconMap.get(changeType);
  const isSameId = fromId === get(detail, 'uuid', null);
  if (isSameId) {
    if (changeType === 'member-remove') {
      possibleIcon = 'group-leave';
    }
    if (changeType === 'member-add-from-invite') {
      possibleIcon = 'group-approved';
    }
  }
  // Use default icon for "... requested to join via group link" added to
  // bounce notification.
  if (changeType === 'admin-approval-bounce' && isLastText) {
    possibleIcon = undefined;
  }
  if (changeType === 'summary') {
    possibleIcon = 'group-summary';
  }
  return possibleIcon || 'group';
}

function GroupV2Detail({
  areWeAdmin,
  blockGroupLinkRequests,
  conversationId,
  detail,
  isLastText,
  fromId,
  groupMemberships,
  groupBannedMemberships,
  groupName,
  i18n,
  ourACI,
  ourPNI,
  renderContact,
  text,
}: {
  areWeAdmin: boolean;
  blockGroupLinkRequests: (
    conversationId: string,
    uuid: UUIDStringType
  ) => unknown;
  conversationId: string;
  detail: GroupV2ChangeDetailType;
  isLastText: boolean;
  groupMemberships?: ReadonlyArray<{
    uuid: UUIDStringType;
    isAdmin: boolean;
  }>;
  groupBannedMemberships?: ReadonlyArray<UUIDStringType>;
  groupName?: string;
  i18n: LocalizerType;
  fromId?: UUIDStringType;
  ourACI?: UUIDStringType;
  ourPNI?: UUIDStringType;
  renderContact: SmartContactRendererType<FullJSXType>;
  text: FullJSXType;
}): JSX.Element {
  const icon = getIcon(detail, isLastText, fromId);
  let buttonNode: ReactNode;

  const [modalState, setModalState] = useState<ModalState>(ModalState.None);
  let modalNode: ReactNode;

  switch (modalState) {
    case ModalState.None:
      modalNode = undefined;
      break;
    case ModalState.ViewingGroupDescription:
      if (detail.type !== 'description' || !detail.description) {
        log.warn(
          'GroupV2Detail: ViewingGroupDescription but missing description or wrong change type'
        );
        modalNode = undefined;
        break;
      }

      modalNode = (
        <Modal
          modalName="GroupV2Change.ViewingGroupDescription"
          hasXButton
          i18n={i18n}
          title={groupName}
          onClose={() => setModalState(ModalState.None)}
        >
          <GroupDescriptionText text={detail.description} />
        </Modal>
      );
      break;
    case ModalState.ConfirmingblockGroupLinkRequests:
      if (
        !isLastText ||
        detail.type !== 'admin-approval-bounce' ||
        !detail.uuid
      ) {
        log.warn(
          'GroupV2Detail: ConfirmingblockGroupLinkRequests but missing uuid or wrong change type'
        );
        modalNode = undefined;
        break;
      }

      modalNode = (
        <ConfirmationDialog
          dialogName="GroupV2Change.confirmBlockLinkRequests"
          title={i18n('PendingRequests--block--title')}
          actions={[
            {
              action: () => blockGroupLinkRequests(conversationId, detail.uuid),
              text: i18n('PendingRequests--block--confirm'),
              style: 'affirmative',
            },
          ]}
          i18n={i18n}
          onClose={() => setModalState(ModalState.None)}
        >
          <Intl
            id="PendingRequests--block--contents"
            i18n={i18n}
            components={{
              name: renderContact(detail.uuid),
            }}
          />
        </ConfirmationDialog>
      );
      break;
    default: {
      const state: never = modalState;
      log.warn(`GroupV2Detail: unexpected modal state ${state}`);
      modalNode = undefined;
      break;
    }
  }

  if (detail.type === 'description' && detail.description) {
    buttonNode = (
      <Button
        onClick={() => setModalState(ModalState.ViewingGroupDescription)}
        size={ButtonSize.Small}
        variant={ButtonVariant.SystemMessage}
      >
        {i18n('view')}
      </Button>
    );
  } else if (
    isLastText &&
    detail.type === 'admin-approval-bounce' &&
    areWeAdmin &&
    detail.uuid &&
    detail.uuid !== ourACI &&
    detail.uuid !== ourPNI &&
    (!fromId || fromId === detail.uuid) &&
    !groupMemberships?.some(item => item.uuid === detail.uuid) &&
    !groupBannedMemberships?.some(uuid => uuid === detail.uuid)
  ) {
    buttonNode = (
      <Button
        onClick={() =>
          setModalState(ModalState.ConfirmingblockGroupLinkRequests)
        }
        size={ButtonSize.Small}
        variant={ButtonVariant.SystemMessage}
      >
        {i18n('PendingRequests--block--button')}
      </Button>
    );
  }

  return (
    <>
      <SystemMessage icon={icon} contents={text} button={buttonNode} />
      {modalNode}
    </>
  );
}

export function GroupV2Change(props: PropsType): ReactElement {
  const {
    areWeAdmin,
    blockGroupLinkRequests,
    change,
    conversationId,
    groupBannedMemberships,
    groupMemberships,
    groupName,
    i18n,
    ourACI,
    ourPNI,
    renderContact,
  } = props;

  return (
    <>
      {renderChange<FullJSXType>(change, {
        i18n,
        ourACI,
        ourPNI,
        renderContact,
        renderString: renderStringToIntl,
      }).map(({ detail, isLastText, text }, index) => {
        return (
          <GroupV2Detail
            areWeAdmin={areWeAdmin}
            blockGroupLinkRequests={blockGroupLinkRequests}
            conversationId={conversationId}
            detail={detail}
            isLastText={isLastText}
            fromId={change.from}
            groupBannedMemberships={groupBannedMemberships}
            groupMemberships={groupMemberships}
            groupName={groupName}
            i18n={i18n}
            // Difficult to find a unique key for this type
            // eslint-disable-next-line react/no-array-index-key
            key={index}
            ourACI={ourACI}
            ourPNI={ourPNI}
            renderContact={renderContact}
            text={text}
          />
        );
      })}
    </>
  );
}
