import { Button, Column, Dialog, ModalContext } from '@8base/boost';
import { ApolloClient, ApolloQueryResult } from 'apollo-client';
import debounce from 'lodash/debounce';
import React from 'react';
import { compose, withApollo } from 'react-apollo';

import { ChatContext, IChatContext, withContext } from 'context';
import { DialogForm, UsersBrowser } from 'shared/components';
import {
  FindOrCreateDmDocument,
  FindOrCreateDmMutation,
  FindOrCreateDmMutationVariables,
  UserContactsPreviewDocument,
  UserContactsPreviewQuery,
  UserDmsPreviewDocument,
  // UsersDmSearchDocument,
  UsersDmSearchQuery,
} from 'shared/graphql/__generated__';
import { makeCancelable } from 'shared/utils';
import { IOption } from 'types';

import { getOperationName } from 'apollo-link';
import cssUtils from '../../../styles/utilities.css';

// -- TYPES

interface IOpenDmDialogProps
  extends Pick<
    IChatContext,
    'user' | 'setChannel' | 'usersFilter' | 'channelMembersFilter'
  > {
  client: ApolloClient<any>;
}

interface IDialogContentProps extends IOpenDmDialogProps {}

interface IDialogContentState {
  data: {
    users: IOption[];
  };
  userSearchText: string;
  usersList: UsersDmSearchQuery['usersList'];
  loadingOptions: boolean;
  submitting: boolean;
}

// -- CONSTANTS

export const OPEN_DM_DIALOG_ID = 'OPEN_DM_DIALOG_ID';
const MAX_USERS_AMOUNT = 8;

// -- COMPONENTS

const cnUsersBrowser = {
  list: cssUtils['u-dialog-browser-list'],
};

class DialogContent extends React.Component<IDialogContentProps, IDialogContentState> {
  static contextType = ModalContext;

  private onSearchDebounced: DialogContent['onSearch'];
  private currentRequest?: ReturnType<typeof makeCancelable>;

  constructor(props: IDialogContentProps) {
    super(props);

    this.state = {
      data: {
        users: [],
      },
      userSearchText: '',
      usersList: {
        items: [],
      },
      loadingOptions: false,
      submitting: false,
    };

    this.onSearchDebounced = debounce(this.onSearch, 200);
  }

  componentDidMount() {
    this.fetchUsers(this.state.userSearchText);
  }

  componentWillUnmount() {
    this.cancelCurrentRequest();
  }

  render() {
    const { data, userSearchText, usersList, loadingOptions, submitting } = this.state;

    const disabled = data.users.length === 0;

    const availableUsers = usersList.items.filter(
      user => data.users.find(el => el.value === user.id) === undefined,
    );

    return (
      <DialogForm onSubmit={this.onSubmit}>
        <Dialog.Header title="Direct messages" onClose={this.closeModal} />

        <Dialog.Body scrollable={true} padding="none">
          <Column gap="sm" offsetX="lg" offsetY="md" alignItems="stretch">
            <div>You can only invite {MAX_USERS_AMOUNT} users.</div>

            <UsersBrowser
              label="Name"
              placeholder="Search"
              className={cnUsersBrowser}
              input={{
                value: data.users.map(el => el.value),
                onChange: this.onSelectChange,
              }}
              options={data.users}
              loading={loadingOptions}
              onInputChange={this.onInputChange}
              inputValue={userSearchText}
              users={availableUsers}
              onItemSelect={this.onItemSelect}
            />
          </Column>
        </Dialog.Body>

        <Dialog.Footer>
          <Button
            color="neutral"
            variant="outlined"
            type="button"
            onClick={this.closeModal}
          >
            Cancel
          </Button>

          <Button color="primary" type="submit" disabled={disabled} loading={submitting}>
            Confirm
          </Button>
        </Dialog.Footer>
      </DialogForm>
    );
  }

  private onSubmit = async (e: any) => {
    e.preventDefault();

    if (!this.props.user || this.state.submitting) {
      return;
    }

    const currentUserId = this.props.user.id;

    const usersIds = this.state.data.users.map(el => el.value);
    usersIds.push(currentUserId);

    this.setState({
      submitting: true,
    });

    const { data }: { data: FindOrCreateDmMutation } = await this.props.client.mutate<
      FindOrCreateDmMutation,
      FindOrCreateDmMutationVariables
    >({
      mutation: FindOrCreateDmDocument,
      variables: {
        usersIds,
        channelMembersFilter: this.props.channelMembersFilter,
      },
      refetchQueries: [getOperationName(UserDmsPreviewDocument) as string],
    });

    this.setState({
      submitting: false,
    });

    if (!data.findOrCreateDM) {
      return;
    }

    const channelIdentity = data.findOrCreateDM.members.items.find(
      el => el.user.id === currentUserId,
    );

    if (!channelIdentity) {
      return;
    }

    this.props.setChannel({
      channelIdentityId: channelIdentity.id,
    });

    this.closeModal();
  };

  private closeModal = () => {
    this.context.closeModal(OPEN_DM_DIALOG_ID);
  };

  private onItemSelect = (option: IOption) => {
    this.setState(s => {
      if (
        s.data.users.length === MAX_USERS_AMOUNT ||
        s.data.users.find(el => el.value === option.value)
      ) {
        return null;
      }

      return {
        data: {
          ...s.data,
          users: [...s.data.users, option],
        },
      };
    });
  };

  private onSelectChange = (values: string[]) => {
    this.setState(s => ({
      data: {
        ...s.data,
        users: Array.isArray(values)
          ? s.data.users.filter(el => values.includes(el.value))
          : [],
      },
    }));
  };

  private onInputChange = (val: string, { action }: { action: string }) => {
    if (action !== 'input-change') {
      return;
    }

    this.setState({
      userSearchText: val,
    });

    this.onSearchDebounced(val);
  };

  private onSearch = (text: string) => {
    this.cancelCurrentRequest();
    this.fetchUsers(text);
  };

  private fetchUsers(text: string) {
    const idNotIn = this.props.user ? [this.props.user.id] : [];

    this.setState({
      loadingOptions: true,
    });

    this.currentRequest = makeCancelable(
      this.props.client.query<UserContactsPreviewQuery>({
        query: UserContactsPreviewDocument,
        variables: {
          filter: this.props.usersFilter,
        },
      }),
    );

    (this.currentRequest.promise as Promise<ApolloQueryResult<UsersDmSearchQuery>>)
      .then(({ data }) => {
        this.setState({
          usersList: data.usersList,
          loadingOptions: false,
        });
      })
      .catch(() => {});
  }

  private cancelCurrentRequest() {
    if (this.currentRequest) {
      this.currentRequest.cancel();
    }
  }
}

// -- MAIN

function OpenDmDialog(props: IOpenDmDialogProps) {
  return (
    <Dialog id={OPEN_DM_DIALOG_ID} size="md">
      <DialogContent {...props} />
    </Dialog>
  );
}

OpenDmDialog.id = OPEN_DM_DIALOG_ID;

export default compose(
  withApollo,
  withContext({
    context: ChatContext,
    keys: ['user', 'setChannel', 'usersFilter', 'channelMembersFilter'],
  }),
)(OpenDmDialog);
