import { Button, Dialog, ModalContext } from '@8base/boost';
import { ApolloClient, ApolloQueryResult, FetchPolicy } 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 {
  ChannelMembersDocument,
  ChannelUpdateDocument,
  ChannelUpdateMutation,
  UserChannelsPreviewDocument,
  UsersSearchDocument,
  UsersSearchQuery,
  UsersSearchQueryVariables,
} 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 IAddToChannelDialogProps {
  user: IChatContext['user'];
  client: ApolloClient<any>;
  usersFilter: IChatContext['usersFilter'];
}

interface IDialogContentProps extends IAddToChannelDialogProps {
  channelId: string;
  channelName: string;
}

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

// -- CONSTANTS

export const ADD_TO_CHANNEL_DIALOG_ID = 'ADD_TO_CHANNEL_DIALOG_ID';

// -- 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, 'network-only');
  }

  componentWillUnmount() {
    this.cancelCurrentRequest();
  }

  render() {
    const { channelName } = this.props;
    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={`Add to ${channelName}`} onClose={this.closeDialog} />

        <Dialog.Body scrollable={true} padding="md">
          <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}
          />
        </Dialog.Body>

        <Dialog.Footer>
          <Button
            color="neutral"
            variant="outlined"
            type="button"
            onClick={this.closeDialog}
          >
            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.state.submitting) {
      return;
    }

    const membersCreate = this.state.data.users.map(el => ({
      user: {
        connect: {
          id: el.value,
        },
      },
    }));

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

    await this.props.client.mutate<ChannelUpdateMutation>({
      mutation: ChannelUpdateDocument,
      refetchQueries: [
        getOperationName(UserChannelsPreviewDocument) as string,
        {
          query: ChannelMembersDocument,
          variables: { id: this.props.channelId, first: 3 },
        },
      ],
      variables: {
        data: {
          id: this.props.channelId,
          members: {
            create: membersCreate,
          },
        },
      },
    });

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

    this.closeDialog();
  };

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

  private onItemSelect = (option: IOption) => {
    this.setState(s => {
      if (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: 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, fetchPolicy?: FetchPolicy) {
    this.setState({
      loadingOptions: true,
    });

    const usersFilter = this.props.usersFilter || {
      id: {
        not_in: [this.props.user ? this.props.user.id : ''],
      },
    };

    const queryVariables: UsersSearchQueryVariables = {
      first: 4,
      searchText: text,
      channelId: this.props.channelId,
      usersFilter,
    };

    this.currentRequest = makeCancelable(
      this.props.client.query<UsersSearchQuery>({
        query: UsersSearchDocument,
        variables: queryVariables,
        fetchPolicy,
      }),
    );

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

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

// -- MAIN

function AddToChannelDialog(props: IAddToChannelDialogProps) {
  return (
    <Dialog id={ADD_TO_CHANNEL_DIALOG_ID} size="md">
      {({ args }: { args: { channelId: string; channelName: string } }) => (
        <DialogContent
          {...props}
          channelId={args.channelId}
          channelName={args.channelName}
        />
      )}
    </Dialog>
  );
}

AddToChannelDialog.id = ADD_TO_CHANNEL_DIALOG_ID;

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