import useActionLogs from '@/analyst/dataHooks/useActionLogs'
import useCategories from '@/analyst/dataHooks/useCategories'
import useMilestoneStates from '@/analyst/dataHooks/useMilestoneStates'
import type {
  ActionLog,
  DefinitionInject,
  EmailDetails,
  EmailThread,
  MilestoneState,
  ToolDetails,
} from '@inject/graphql/fragment-types'
import useChannelTypeEnabled from '@inject/graphql/utils/useChannelTypeEnabled'
import EmailReceived from '@inject/shared/svg/email-received.svg?react'
import EmailSent from '@inject/shared/svg/email-sent.svg?react'
import Flag from '@inject/shared/svg/flag.svg?react'
import notEmpty from '@inject/shared/utils/notEmpty'
import type { Dispatch, FC } from 'react'
import { useCallback, useContext, useMemo } from 'react'
import ExerciseContext from '../ExerciseContext'
import LegendText from '../Plots/LegendText'
import ResponsivePlot from '../Plots/ResponsivePlot'
import type { ScatterPlotDataRenderer } from '../Plots/types'
import SVGContext from '../SVGContext'
import {
  ACTION_TYPES,
  LEGEND_ELEMENT_SIZE,
  NOTHING_SELECTED_OPACITY,
  NOT_SELECTED_OPACITY,
  SELECTED_OPACITY,
  actionTypeColor,
  getTeamById,
  legendIconProps,
} from '../utilities'
import ActionIcon from './ActionIcon'
import CategoryLine from './CategoryLine'
import EmailIcon from './EmailIcon'
import MilestoneIcon from './MilestoneIcon'
import type {
  selectedReducerActionProps,
  selectedReducerStateProps,
} from './selectedReducer'

interface OverviewPlotProps {
  selectedState: selectedReducerStateProps
  selectedDispatch: Dispatch<selectedReducerActionProps>
}

const OverviewPlot: FC<OverviewPlotProps> = ({
  selectedState,
  selectedDispatch,
}) => {
  const { exercise } = useContext(ExerciseContext)
  const { dependencies } = useContext(SVGContext)
  const milestoneStates = useMilestoneStates()
  const categories = useCategories()
  const actionLogs = useActionLogs()
  const emailsEnabled = useChannelTypeEnabled('EMAIL', {
    variables: { exerciseId: exercise.id },
  })

  const emailLegendItems = emailsEnabled
    ? [
        <g key='email received'>
          <EmailReceived {...legendIconProps} />
          <LegendText text='EMAIL RECEIVED' />
        </g>,
        <g key='email sent'>
          <EmailSent {...legendIconProps} />
          <LegendText text='EMAIL SENT' />
        </g>,
      ]
    : []

  const legendItems = [
    <g key='milestone'>
      <Flag {...legendIconProps} />
      <LegendText text='MILESTONE' />
    </g>,
    ...emailLegendItems,
    ...ACTION_TYPES.map(actionType => (
      <g key={actionType}>
        <circle
          r={LEGEND_ELEMENT_SIZE / 2}
          fill={actionTypeColor(actionType)}
        />
        <LegendText text={actionType} />
      </g>
    )),
  ]

  const getActionLogOpacity = useCallback(
    (actionLog: ActionLog) => {
      if (!selectedState.selectedDataType) return NOTHING_SELECTED_OPACITY
      if (selectedState.selectedDataType !== 'ACTIONS')
        return NOT_SELECTED_OPACITY
      if (!selectedState.actionLog)
        throw new Error('actionLog null when ACTIONS selected')
      if (selectedState.actionLog.type === actionLog.type) {
        if (selectedState.actionLog.type !== 'TOOL') {
          return SELECTED_OPACITY
        }
        if (
          (selectedState.actionLog.details as ToolDetails).tool.id ===
          (actionLog.details as ToolDetails).tool.id
        ) {
          return SELECTED_OPACITY
        }
        return NOT_SELECTED_OPACITY
      }
      return NOT_SELECTED_OPACITY
    },
    [selectedState]
  )

  const getMilestoneOpacity = useCallback(
    (milestoneState: MilestoneState) => {
      if (!selectedState.selectedDataType) return NOTHING_SELECTED_OPACITY
      if (selectedState.selectedDataType !== 'MILESTONES')
        return NOT_SELECTED_OPACITY
      if (!selectedState.milestoneState) {
        throw new Error('milestoneState null when MILESTONES selected')
      }
      if (
        selectedState.milestoneState.milestone.id ===
        milestoneState.milestone.id
      ) {
        return SELECTED_OPACITY
      }
      return NOT_SELECTED_OPACITY
    },
    [selectedState]
  )

  const getEmailOpacity = useCallback(
    (emailThread: EmailThread) => {
      if (!selectedState.selectedDataType) return NOTHING_SELECTED_OPACITY
      if (selectedState.selectedDataType !== 'EMAILS')
        return NOT_SELECTED_OPACITY
      if (!selectedState.actionLog)
        throw new Error('actionLog null when EMAILS selected')
      if (selectedState.actionLog.type !== 'EMAIL')
        throw new Error("actionLog.type !== 'EMAIL' when EMAILS selected")
      if (
        (selectedState.actionLog.details as EmailDetails).thread.id ===
        emailThread.id
      )
        return SELECTED_OPACITY
      return NOT_SELECTED_OPACITY
    },
    [selectedState]
  )

  const injectGroups = useMemo(
    () =>
      categories.reduce<DefinitionInject[][]>((accumulator, category) => {
        const injectGroupIndex = accumulator.findIndex(
          (injectGroup: DefinitionInject[]) =>
            injectGroup[0].delay === category.delay &&
            injectGroup[0].time === category.time
        )

        return injectGroupIndex === -1
          ? [...accumulator, [category]]
          : [
              ...accumulator.slice(0, injectGroupIndex),
              [...accumulator[injectGroupIndex], category],
              ...accumulator.slice(injectGroupIndex + 1),
            ]
      }, []),
    [categories]
  )

  const injectGroupsRenderer: ScatterPlotDataRenderer = useCallback(
    ({ xScale, height }) =>
      injectGroups.map(injectGroup => (
        <CategoryLine
          key={injectGroup[0].id}
          injectGroup={injectGroup}
          onClick={() =>
            selectedDispatch({
              type: 'selectCategories',
              injectGroup: injectGroup,
            })
          }
          xScale={xScale}
          height={height}
        />
      )),
    [injectGroups, selectedDispatch]
  )

  const actionLogRenderer: ScatterPlotDataRenderer = useCallback(
    ({ xScale, yScale, dotSize }) =>
      actionLogs.map(actionLog =>
        actionLog.type === 'EMAIL' ? (
          <EmailIcon
            key={actionLog.id}
            team={actionLog.team}
            emailDetails={actionLog.details as EmailDetails}
            opacity={getEmailOpacity(
              (actionLog.details as EmailDetails).thread
            )}
            onClick={() =>
              selectedDispatch({
                type: 'selectEmails',
                actionLog,
              })
            }
            xScale={xScale}
            yScale={yScale}
            dotSize={dotSize}
          />
        ) : (
          <ActionIcon
            key={actionLog.id}
            actionLog={actionLog}
            opacity={getActionLogOpacity(actionLog)}
            onClick={() =>
              selectedDispatch({ type: 'selectActions', actionLog })
            }
            xScale={xScale}
            yScale={yScale}
            dotSize={dotSize}
          />
        )
      ),
    [actionLogs, getActionLogOpacity, getEmailOpacity, selectedDispatch]
  )

  const milestonesRenderer: ScatterPlotDataRenderer = useCallback(
    ({ xScale, yScale, dotSize }) =>
      milestoneStates
        .filter(
          milestoneState =>
            milestoneState.reached && milestoneState.timestampReached
        )
        .map(milestoneState =>
          milestoneState.teamIds?.filter(notEmpty).map(teamId => (
            <MilestoneIcon
              key={`${milestoneState.milestone.id}:${teamId}`}
              milestone={milestoneState}
              teamName={getTeamById(exercise.teams, teamId)?.name || ''}
              opacity={getMilestoneOpacity(milestoneState)}
              onClick={() =>
                selectedDispatch({
                  type: 'selectMilestones',
                  milestoneState,
                })
              }
              xScale={xScale}
              yScale={yScale}
              dotSize={dotSize}
            />
          ))
        ),
    [exercise.teams, getMilestoneOpacity, milestoneStates, selectedDispatch]
  )

  const dataRenderer: ScatterPlotDataRenderer = useCallback(
    data => (
      <>
        {injectGroupsRenderer(data)}
        {actionLogRenderer(data)}
        {milestonesRenderer(data)}
      </>
    ),
    [actionLogRenderer, injectGroupsRenderer, milestonesRenderer]
  )

  return (
    <ResponsivePlot
      type='scatter'
      id='OverviewPlot'
      groupNames={exercise.teams.map(team => team.name)}
      details={{
        onResetSelection: () => selectedDispatch({ type: 'resetSelection' }),
        legendItems: legendItems,
        dataRenderer,
      }}
      dependencies={[exercise.teams.length, emailsEnabled, ...dependencies]}
    />
  )
}

export default OverviewPlot
