import Description from '@/components/Description'
import { MenuItem } from '@blueprintjs/core'
import type { ItemRenderer } from '@blueprintjs/select'
import { MultiSelect } from '@blueprintjs/select'
import type { EmailParticipant } from '@inject/graphql/fragment-types'
import type { ResultOf, VariablesOf } from '@inject/graphql/graphql'
import { ValidateEmailAddress } from '@inject/graphql/queries'
import { useClient } from '@inject/graphql/urql/client'
import { notify } from '@inject/shared/notification/engine'
import type { Dispatch, FC, MouseEventHandler, SetStateAction } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import type { ExtendedItemRenderer } from '../typing'

interface EmailContactSelectorProps {
  emailContacts: EmailParticipant[]
  selectedContacts: string[]
  setSelectedContacts: Dispatch<SetStateAction<string[]>>
  inInstructor: boolean
  exerciseId: string
  placeholder?: string
  senderAddress: string
}

const EmailContactSelector: FC<EmailContactSelectorProps> = ({
  placeholder,
  emailContacts,
  selectedContacts,
  setSelectedContacts,
  inInstructor,
  exerciseId,
  senderAddress,
}) => {
  const [query, setQuery] = useState('')
  const client = useClient()

  const suggestedItems = useMemo(
    () =>
      emailContacts
        .map(contact => contact.address)
        .filter(address => address !== senderAddress),
    [emailContacts, senderAddress]
  )
  const selectedItems = useMemo(
    () => selectedContacts.filter(contact => contact !== senderAddress),
    [selectedContacts, senderAddress]
  )

  const itemRenderer: ExtendedItemRenderer<string> = useCallback(
    (address, { handleClick, handleFocus, modifiers, description }) => (
      <MenuItem
        active={modifiers.active}
        disabled={modifiers.disabled}
        onClick={handleClick}
        onFocus={handleFocus}
        roleStructure='listoption'
        selected={selectedContacts.includes(address)}
        shouldDismissPopover={false}
        text={
          <Description description={description} hideDescription={!description}>
            {address}
          </Description>
        }
        key={address}
      />
    ),
    [selectedContacts]
  )

  const instructorItemRenderer: ItemRenderer<string> = useCallback(
    (address, props) => {
      const description = emailContacts.find(
        contact => contact.address === address
      )?.definitionAddress?.description

      return itemRenderer(address, { ...props, description })
    },
    [emailContacts, itemRenderer]
  )

  const itemDisabled = useCallback(
    (address: string) => address === senderAddress,
    [senderAddress]
  )
  const onItemSelect = useCallback(
    async (address: string) => {
      // todo: this is peculiar for that enter bug
      const { data } = await client.query<
        ResultOf<typeof ValidateEmailAddress>,
        VariablesOf<typeof ValidateEmailAddress>
      >(ValidateEmailAddress, {
        exerciseId,
        address: address,
      })

      if (!data?.validateEmailAddress) {
        notify('Invalid email address', { intent: 'danger' })
        return
      }

      setSelectedContacts(prev => {
        if (prev.includes(address)) {
          return prev.filter(contact => contact !== address)
        }
        return [...prev, address]
      })
    },
    [client, exerciseId, setSelectedContacts]
  )
  const onRemove = useCallback(
    (address: string) =>
      setSelectedContacts(prev => prev.filter(contact => contact !== address)),
    [setSelectedContacts]
  )

  const createNewItemFromQuery: (query: string) => string | string[] =
    useCallback((query: string) => query, [])
  const createNewItemRenderer: (
    query: string,
    active: boolean,
    handleClick: MouseEventHandler<HTMLElement>
  ) => undefined | React.JSX.Element = useCallback(
    (
      query: string,
      active: boolean,
      handleClick: MouseEventHandler<HTMLElement>
    ) =>
      query ? (
        <MenuItem
          active={active}
          onClick={handleClick}
          roleStructure='listoption'
          shouldDismissPopover={false}
          text={query}
          key='new-item'
        />
      ) : (
        <></>
      ),
    []
  )

  const handleBlur = useCallback(() => {
    if (query && !selectedItems.includes(query)) {
      onItemSelect(query)
    }
    setQuery('')
  }, [onItemSelect, query, selectedItems])

  return (
    <MultiSelect<string>
      query={query}
      onQueryChange={query => setQuery(query.trim())}
      placeholder={placeholder}
      itemRenderer={inInstructor ? instructorItemRenderer : itemRenderer}
      items={suggestedItems}
      itemDisabled={itemDisabled}
      itemPredicate={(query, item) => item.includes(query)}
      onItemSelect={onItemSelect}
      onRemove={onRemove}
      popoverProps={{
        minimal: true,
      }}
      resetOnSelect
      selectedItems={selectedItems}
      tagRenderer={i => i}
      popoverContentProps={{
        style: { maxHeight: '50vh', overflowY: 'auto', overflowX: 'hidden' },
      }}
      createNewItemFromQuery={createNewItemFromQuery}
      createNewItemRenderer={createNewItemRenderer}
      /*
       * Achieves adding items without pressing Enter.
       *
       * Can't use tagInputProps.addOnBlur, because the onAdd handler is
       * overriden in the implementation of MultiSelect:
       * https://github.com/palantir/blueprint/blob/develop/packages/select/src/components/multi-select/multiSelect.tsx#L346
       *
       * (this is possibly a bug in BlueprintJS)
       */
      popoverTargetProps={{
        onBlur: handleBlur,
      }}
    />
  )
}

export default EmailContactSelector
