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

import React, { useReducer } from 'react';
import { without } from 'lodash';

import type { LocalizerType } from '../../../types/Util';
import {
  AddGroupMemberErrorDialog,
  AddGroupMemberErrorDialogMode,
} from '../../AddGroupMemberErrorDialog';
import type { SmartChooseGroupMembersModalPropsType } from '../../../state/smart/ChooseGroupMembersModal';
import type { SmartConfirmAdditionsModalPropsType } from '../../../state/smart/ConfirmAdditionsModal';
import {
  toggleSelectedContactForGroupAddition,
  OneTimeModalState,
} from '../../../groups/toggleSelectedContactForGroupAddition';
import { missingCaseError } from '../../../util/missingCaseError';
import type { RequestState } from './util';

type PropsType = {
  clearRequestError: () => void;
  conversationIdsAlreadyInGroup: Set<string>;
  groupTitle: string;
  i18n: LocalizerType;
  makeRequest: (conversationIds: ReadonlyArray<string>) => Promise<void>;
  onClose: () => void;
  requestState: RequestState;
  maxGroupSize: number;
  maxRecommendedGroupSize: number;

  renderChooseGroupMembersModal: (
    props: SmartChooseGroupMembersModalPropsType
  ) => JSX.Element;
  renderConfirmAdditionsModal: (
    props: SmartConfirmAdditionsModalPropsType
  ) => JSX.Element;
};

enum Stage {
  ChoosingContacts,
  ConfirmingAdds,
}

type StateType = {
  maxGroupSize: number;
  maxRecommendedGroupSize: number;
  maximumGroupSizeModalState: OneTimeModalState;
  recommendedGroupSizeModalState: OneTimeModalState;
  searchTerm: string;
  selectedConversationIds: ReadonlyArray<string>;
  stage: Stage;
};

enum ActionType {
  CloseMaximumGroupSizeModal,
  CloseRecommendedMaximumGroupSizeModal,
  ConfirmAdds,
  RemoveSelectedContact,
  ReturnToContactChooser,
  ToggleSelectedContact,
  UpdateSearchTerm,
}

type Action =
  | { type: ActionType.CloseMaximumGroupSizeModal }
  | { type: ActionType.CloseRecommendedMaximumGroupSizeModal }
  | { type: ActionType.ConfirmAdds }
  | { type: ActionType.ReturnToContactChooser }
  | { type: ActionType.RemoveSelectedContact; conversationId: string }
  | {
      type: ActionType.ToggleSelectedContact;
      conversationId: string;
      numberOfContactsAlreadyInGroup: number;
    }
  | { type: ActionType.UpdateSearchTerm; searchTerm: string };

// `<ConversationDetails>` isn't currently hooked up to Redux, but that's not desirable in
//   the long term (see DESKTOP-1260). For now, this component has internal state with a
//   reducer. Hopefully, this will make things easier to port to Redux in the future.
function reducer(
  state: Readonly<StateType>,
  action: Readonly<Action>
): StateType {
  switch (action.type) {
    case ActionType.CloseMaximumGroupSizeModal:
      return {
        ...state,
        maximumGroupSizeModalState: OneTimeModalState.Shown,
      };
    case ActionType.CloseRecommendedMaximumGroupSizeModal:
      return {
        ...state,
        recommendedGroupSizeModalState: OneTimeModalState.Shown,
      };
    case ActionType.ConfirmAdds:
      return {
        ...state,
        stage: Stage.ConfirmingAdds,
      };
    case ActionType.ReturnToContactChooser:
      return {
        ...state,
        stage: Stage.ChoosingContacts,
      };
    case ActionType.RemoveSelectedContact:
      return {
        ...state,
        selectedConversationIds: without(
          state.selectedConversationIds,
          action.conversationId
        ),
      };
    case ActionType.ToggleSelectedContact:
      return {
        ...state,
        ...toggleSelectedContactForGroupAddition(action.conversationId, {
          maxGroupSize: state.maxGroupSize,
          maxRecommendedGroupSize: state.maxRecommendedGroupSize,
          maximumGroupSizeModalState: state.maximumGroupSizeModalState,
          numberOfContactsAlreadyInGroup: action.numberOfContactsAlreadyInGroup,
          recommendedGroupSizeModalState: state.recommendedGroupSizeModalState,
          selectedConversationIds: state.selectedConversationIds,
        }),
      };
    case ActionType.UpdateSearchTerm:
      return {
        ...state,
        searchTerm: action.searchTerm,
      };
    default:
      throw missingCaseError(action);
  }
}

export function AddGroupMembersModal({
  clearRequestError,
  conversationIdsAlreadyInGroup,
  groupTitle,
  i18n,
  onClose,
  makeRequest,
  maxGroupSize,
  maxRecommendedGroupSize,
  requestState,
  renderChooseGroupMembersModal,
  renderConfirmAdditionsModal,
}: PropsType): JSX.Element {
  const numberOfContactsAlreadyInGroup = conversationIdsAlreadyInGroup.size;
  const isGroupAlreadyFull = numberOfContactsAlreadyInGroup >= maxGroupSize;
  const isGroupAlreadyOverRecommendedMaximum =
    numberOfContactsAlreadyInGroup >= maxRecommendedGroupSize;

  const [
    {
      maximumGroupSizeModalState,
      recommendedGroupSizeModalState,
      searchTerm,
      selectedConversationIds,
      stage,
    },
    dispatch,
  ] = useReducer(reducer, {
    maxGroupSize,
    maxRecommendedGroupSize,
    maximumGroupSizeModalState: isGroupAlreadyFull
      ? OneTimeModalState.Showing
      : OneTimeModalState.NeverShown,
    recommendedGroupSizeModalState: isGroupAlreadyOverRecommendedMaximum
      ? OneTimeModalState.Shown
      : OneTimeModalState.NeverShown,
    searchTerm: '',
    selectedConversationIds: [],
    stage: Stage.ChoosingContacts,
  });

  if (maximumGroupSizeModalState === OneTimeModalState.Showing) {
    return (
      <AddGroupMemberErrorDialog
        i18n={i18n}
        maximumNumberOfContacts={maxGroupSize}
        mode={AddGroupMemberErrorDialogMode.MaximumGroupSize}
        onClose={() => {
          dispatch({ type: ActionType.CloseMaximumGroupSizeModal });
        }}
      />
    );
  }

  if (recommendedGroupSizeModalState === OneTimeModalState.Showing) {
    return (
      <AddGroupMemberErrorDialog
        i18n={i18n}
        mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
        onClose={() => {
          dispatch({
            type: ActionType.CloseRecommendedMaximumGroupSizeModal,
          });
        }}
        recommendedMaximumNumberOfContacts={maxRecommendedGroupSize}
      />
    );
  }

  switch (stage) {
    case Stage.ChoosingContacts: {
      // See note above: these will soon become Redux actions.
      const confirmAdds = () => {
        dispatch({ type: ActionType.ConfirmAdds });
      };
      const removeSelectedContact = (conversationId: string) => {
        dispatch({
          type: ActionType.RemoveSelectedContact,
          conversationId,
        });
      };
      const setSearchTerm = (term: string) => {
        dispatch({
          type: ActionType.UpdateSearchTerm,
          searchTerm: term,
        });
      };
      const toggleSelectedContact = (conversationId: string) => {
        dispatch({
          type: ActionType.ToggleSelectedContact,
          conversationId,
          numberOfContactsAlreadyInGroup,
        });
      };

      return renderChooseGroupMembersModal({
        confirmAdds,
        selectedConversationIds,
        conversationIdsAlreadyInGroup,
        maxGroupSize,
        onClose,
        removeSelectedContact,
        searchTerm,
        setSearchTerm,
        toggleSelectedContact,
      });
    }
    case Stage.ConfirmingAdds: {
      const onCloseConfirmationDialog = () => {
        dispatch({ type: ActionType.ReturnToContactChooser });
        clearRequestError();
      };

      return renderConfirmAdditionsModal({
        groupTitle,
        makeRequest: () => {
          void makeRequest(selectedConversationIds);
        },
        onClose: onCloseConfirmationDialog,
        requestState,
        selectedConversationIds,
      });
    }
    default:
      throw missingCaseError(stage);
  }
}
