import { MenuItem } from '@blueprintjs/core'
import type { ItemRenderer, ItemRendererProps } from '@blueprintjs/select'
import { MultiSelect } from '@blueprintjs/select'
import type { MilestoneState } from '@inject/graphql/fragment-types'
import { useTypedQuery } from '@inject/graphql/graphql'
import { GetTeamMilestones } from '@inject/graphql/queries'
import notEmpty from '@inject/shared/utils/notEmpty'
import type { Dispatch, ReactNode, SetStateAction } from 'react'
import { memo, useEffect, useState } from 'react'

/*
 * I'm leaving `.name` here instead of `.displayName`, because that's how it's
 * shown in templates (because activateMilestone and deactivateMilestone are
 * strings containing the definition names)
 */

export interface MilestoneOption {
  milestoneState: MilestoneState
  selected: boolean
}

const milestoneOptionsToString = (
  milestoneOptions: MilestoneOption[],
  reached: boolean
) =>
  milestoneOptions
    .filter(milestoneOption => milestoneOption.selected)
    .filter(milestoneOption =>
      reached
        ? milestoneOption.milestoneState.reached
        : !milestoneOption.milestoneState.reached
    )
    .map(milestoneOption => milestoneOption.milestoneState.milestone.name)
    .join(' ')

const getMilestoneOptionText = (milestoneOption: MilestoneOption) =>
  milestoneOption.milestoneState.reached
    ? `not ${milestoneOption.milestoneState.milestone.name}`
    : milestoneOption.milestoneState.milestone.name

const getMilestoneNameFromTag = (tag: ReactNode) => {
  const name = tag?.valueOf().toString() || ''
  return name.startsWith('not ') ? name.substring(4) : name
}

const MilestoneOptionRenderer: ItemRenderer<MilestoneOption> = (
  milestoneOption,
  { handleClick, handleFocus, modifiers, ref }: ItemRendererProps
) => {
  if (!modifiers.matchesPredicate) {
    return null
  }
  return (
    <MenuItem
      key={milestoneOption.milestoneState.id}
      text={getMilestoneOptionText(milestoneOption)}
      selected={milestoneOption.selected}
      roleStructure='listoption'
      shouldDismissPopover={false}
      onClick={handleClick}
      onFocus={handleFocus}
      active={modifiers.active}
      disabled={modifiers.disabled}
      ref={ref}
    />
  )
}

interface MilestoneSelectorProps {
  teamId: string
  activateMilestone: string
  deactivateMilestone: string
  setActivateMilestone: Dispatch<SetStateAction<string>>
  setDeactivateMilestone: Dispatch<SetStateAction<string>>
}

const MilestoneSelector = ({
  teamId,
  activateMilestone,
  deactivateMilestone,
  setActivateMilestone,
  setDeactivateMilestone,
}: MilestoneSelectorProps) => {
  const [{ data }] = useTypedQuery({
    query: GetTeamMilestones,
    variables: {
      teamId,
    },
  })

  const [milestoneOptions, setMilestoneOptions] = useState<MilestoneOption[]>(
    (data?.teamMilestones || [])
      .filter(notEmpty)
      .map(milestoneState => ({ milestoneState, selected: false }))
  )

  useEffect(() => {
    const activateMilestoneNames = activateMilestone.split(' ')
    const deactivateMilestoneNames = deactivateMilestone.split(' ')

    setMilestoneOptions(prev =>
      prev.map(milestoneOption => ({
        ...milestoneOption,
        selected:
          (!milestoneOption.milestoneState.reached &&
            activateMilestoneNames.includes(
              milestoneOption.milestoneState.milestone.name
            )) ||
          (milestoneOption.milestoneState.reached &&
            deactivateMilestoneNames.includes(
              milestoneOption.milestoneState.milestone.name
            )),
      }))
    )
  }, [activateMilestone, deactivateMilestone])

  const updateActivateMilestone = (milestoneOptions: MilestoneOption[]) => {
    setActivateMilestone(milestoneOptionsToString(milestoneOptions, false))
    setDeactivateMilestone(milestoneOptionsToString(milestoneOptions, true))
  }

  const handleItemChange = (
    milestoneOption: MilestoneOption,
    selected: boolean
  ) => {
    const index = milestoneOptions.indexOf(milestoneOption)

    setMilestoneOptions(prev => {
      const newMilestoneOptions = [
        ...prev.slice(0, index),
        { ...prev[index], selected },
        ...prev.slice(index + 1),
      ]
      updateActivateMilestone(newMilestoneOptions)
      return newMilestoneOptions
    })
  }

  return (
    <MultiSelect<MilestoneOption>
      placeholder='Reach milestones'
      items={milestoneOptions}
      itemRenderer={MilestoneOptionRenderer}
      onItemSelect={milestoneOption => {
        handleItemChange(milestoneOption, true)
      }}
      tagInputProps={{
        onRemove: tag => {
          const index = milestoneOptions.indexOf(
            milestoneOptions.find(
              milestoneOption =>
                milestoneOption.milestoneState.milestone.name ===
                getMilestoneNameFromTag(tag)
            ) as MilestoneOption
          )
          // TODO: not ideal, searches for the index twice
          handleItemChange(milestoneOptions[index], false)
        },
        tagProps: {
          minimal: true,
        },
      }}
      tagRenderer={getMilestoneOptionText}
      selectedItems={milestoneOptions.filter(
        milestoneOption => milestoneOption.selected
      )}
      itemPredicate={(query, milestoneOption) =>
        milestoneOption.milestoneState.milestone.name
          .toLowerCase()
          .includes(query.toLowerCase())
      }
      resetOnSelect
      onClear={() => {
        setMilestoneOptions(prev => {
          const newMilestoneOptions = prev.map(milestoneOption => ({
            ...milestoneOption,
            selected: false,
          }))
          updateActivateMilestone(newMilestoneOptions)
          return newMilestoneOptions
        })
      }}
      menuProps={{
        'aria-label': 'milestones',
      }}
      noResults={
        <MenuItem
          disabled
          text='No matching milestones.'
          roleStructure='listoption'
        />
      }
      popoverProps={{
        minimal: true,
      }}
      popoverContentProps={{
        style: { maxHeight: '50vh', overflowY: 'auto', overflowX: 'hidden' },
      }}
    />
  )
}

export default memo(
  MilestoneSelector,
  (prev, next) =>
    prev.teamId === next.teamId &&
    prev.activateMilestone === next.activateMilestone &&
    prev.deactivateMilestone === next.deactivateMilestone
)
