import { Button, Column, 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 } from 'shared/components';
import ChannelsBrowser from 'shared/components/channels-browser';
import {
  ChannelMemberCreateDocument,
  ChannelMemberCreateMutation,
  ChannelMemberCreateMutationVariables,
  ChannelsSearchDocument,
  ChannelsSearchQuery,
  ChannelsSearchQueryVariables,
  UserChannelsPreviewDocument,
} from 'shared/graphql/__generated__';
import { log, makeCancelable } from 'shared/utils';
import { IOption } from 'types';

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

// -- TYPES

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

interface IDialogContentProps extends IChannelsSearchDialogProps {}

interface IDialogContentState {
  data: {
    channel?: IOption | null;
  };
  searchText: string;
  channelsList: ChannelsSearchQuery['channelsList'];
  loadingOptions: boolean;
  submitting: boolean;
}

// -- CONSTANTS

export const CHANNELS_SEARCH_DIALOG_ID = 'CHANNELS_SEARCH_DIALOG_ID';

// -- COMPONENTS

const cnSelectBrowser = {
  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: {
        channel: null,
      },
      searchText: '',
      channelsList: {
        items: [],
      },
      loadingOptions: false,
      submitting: false,
    };

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

  componentDidMount() {
    this.fetchChannels(this.state.searchText, 'network-only');
  }

  componentWillUnmount() {
    this.cancelCurrentRequest();
  }

  render() {
    const { data, searchText, channelsList, loadingOptions, submitting } = this.state;
    const selectedChannelId = data.channel && data.channel.value;
    const disabled = !selectedChannelId;

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

        <Dialog.Body scrollable={true} padding="none">
          <Column gap="sm" offsetX="lg" offsetY="md" alignItems="stretch">
            <ChannelsBrowser
              label="Name"
              placeholder="Search"
              className={cnSelectBrowser}
              input={{
                value: selectedChannelId,
              }}
              loading={loadingOptions}
              onInputChange={this.onInputChange}
              inputValue={searchText}
              onItemSelect={this.onItemSelect}
              channels={channelsList.items}
            />
          </Column>
        </Dialog.Body>

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

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

    const selectedChannelId = this.state.data.channel && this.state.data.channel.value;
    const { user, client } = this.props;

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

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

    let response: { data: ChannelMemberCreateMutation } | undefined;

    try {
      response = await client.mutate<
        ChannelMemberCreateMutation,
        ChannelMemberCreateMutationVariables
      >({
        mutation: ChannelMemberCreateDocument,
        variables: {
          data: {
            channel: {
              connect: { id: selectedChannelId },
            },
            user: {
              connect: { id: user.id },
            },
          },
        },
        refetchQueries: [getOperationName(UserChannelsPreviewDocument) as string],
      });
    } catch (e) {
      log('error', e);
    }

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

    if (!response) {
      return;
    }

    this.props.setChannel({
      channelIdentityId: response.data.channelMemberCreate.id as string,
    });

    this.closeModal();
  };

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

  private onItemSelect = (option: IOption) => {
    this.onSearchDebounced('');

    this.setState({
      data: {
        channel: option,
      },
      searchText: '',
    });
  };

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

    this.onSearchDebounced(val);
  };

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

  private fetchChannels(text: string, fetchPolicy?: FetchPolicy) {
    const userId = this.props.user ? this.props.user.id : '';

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

    this.currentRequest = makeCancelable(
      this.props.client.query<ChannelsSearchQuery, ChannelsSearchQueryVariables>({
        query: ChannelsSearchDocument,
        variables: {
          first: 10,
          searchText: text,
          userId,
        },
        fetchPolicy,
      }),
    );

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

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

// -- MAIN

function ChannelsSearchDialog(props: IChannelsSearchDialogProps) {
  return (
    <Dialog id={CHANNELS_SEARCH_DIALOG_ID} size="md">
      <DialogContent {...props} />
    </Dialog>
  );
}

ChannelsSearchDialog.id = CHANNELS_SEARCH_DIALOG_ID;

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