import type { OptionProps } from '@blueprintjs/core'
import { Button, Callout, HTMLSelect, Icon } from '@blueprintjs/core'
import { css } from '@emotion/css'
import { useLiveQuery } from 'dexie-react-hooks'
import { isEqual } from 'lodash'
import { memo, useCallback, useEffect, useMemo, useState, type FC } from 'react'
import TooltipLabel from '../Tooltips/TooltipLabel'
import { getMilestonesWithNames } from '../indexeddb/joins'
import type { ExtendedLabel } from '../types'
import {
  CLOSING_BRACKET,
  DEFAULT_OPTION,
  NOT,
  OPENING_BRACKET,
  OPERATORS,
  getExpression,
  getVariables,
} from '../utils'
import ExpressionBlock from './ExpressionBlock'
import useValidateExpression from './useValidateExpression'

const condition = css`
  display: flex;
  align-items: start;
  margin: 0.5rem 0 1rem;
`

const expressionArea = css`
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  padding: 0 0.5rem 0.5rem;
  margin-right: 0.5rem;
`

const nextBlock = css`
  margin-left: 0.5rem;
  padding-top: 0.5rem;
`

const errorMessage = css`
  display: flex;
  align-items: center;
  margin-bottom: 1rem;
`

interface ExpressionBuilderProps {
  label: ExtendedLabel
  initExpression?: number[]
  onExpressionChange: (value: number[]) => void
}

const ExpressionBuilder: FC<ExpressionBuilderProps> = ({
  label,
  initExpression,
  onExpressionChange,
}) => {
  const milestones = useLiveQuery(() => getMilestonesWithNames(), [], [])

  const [milestoneCondition, setMilestoneCondition] = useState<number[]>(
    initExpression || []
  )
  const [expression, setExpression] = useState<OptionProps[]>([])
  const [openBrackets, setOpenBrackets] = useState(0)
  const [lastBlock, setLastBlock] = useState<OptionProps | undefined>(undefined)
  const [option, setOption] = useState<OptionProps>(DEFAULT_OPTION)

  const { isValid, error } = useValidateExpression(milestoneCondition)
  const variables = useMemo(() => getVariables(milestones), [milestones])

  useEffect(() => {
    if (variables.length > 0 && initExpression && initExpression.length > 0) {
      const newExpression = getExpression(initExpression, variables)
      if (newExpression.length > 0) {
        setExpression(newExpression)
        setLastBlock(newExpression[newExpression.length - 1])
      }
    }
  }, [variables, initExpression])

  useEffect(() => {
    const condition = expression?.map(block => Number(block.value))
    setMilestoneCondition(condition)
    onExpressionChange(condition)
  }, [expression])

  const clearExpression = useCallback(() => {
    setExpression([])
    setOpenBrackets(0)
    setLastBlock(undefined)
  }, [])

  const updateBrackets = useCallback(
    (block: OptionProps, increment: number) => {
      if (isEqual(block, OPENING_BRACKET)) {
        setOpenBrackets(openBrackets + increment)
      } else if (isEqual(block, CLOSING_BRACKET)) {
        setOpenBrackets(openBrackets - increment)
      }
    },
    [openBrackets]
  )

  const addBlock = useCallback(
    (block: OptionProps) => {
      if (isEqual(block, DEFAULT_OPTION)) {
        return
      }
      setExpression([...expression, block])
      updateBrackets(block, 1)
      setOption(DEFAULT_OPTION)
      setLastBlock(block)
    },
    [expression, updateBrackets]
  )

  const removeBlock = useCallback(
    (index: number) => {
      if (index === expression.length - 1) {
        const prevBlock =
          expression.length > 1 ? expression[expression.length - 2] : undefined
        setLastBlock(prevBlock)
      }
      const block = expression[index]
      setExpression(expression.filter((_, i) => i !== index))
      updateBrackets(block, -1)
    },
    [expression, updateBrackets]
  )

  const modifyBlock = useCallback(
    (index: number, newBlock: OptionProps) => {
      if (index === expression.length - 1) {
        setLastBlock(newBlock)
      }
      const oldBlock = expression[index]
      const newExpression = [...expression]
      newExpression[index] = newBlock
      setExpression(newExpression)
      updateBrackets(oldBlock, -1)
      updateBrackets(newBlock, 1)
    },
    [expression, updateBrackets]
  )

  const options = useMemo(() => {
    if (
      expression.length === 0 ||
      OPERATORS.find(value => isEqual(value, lastBlock)) ||
      isEqual(lastBlock, OPENING_BRACKET)
    ) {
      return [...variables, OPENING_BRACKET, NOT]
    } else if (isEqual(lastBlock, NOT)) {
      return [...variables, OPENING_BRACKET]
    } else if (
      variables.find(value => isEqual(value, lastBlock)) ||
      isEqual(lastBlock, CLOSING_BRACKET)
    ) {
      return [...OPERATORS, ...(openBrackets > 0 ? [CLOSING_BRACKET] : [])]
    }
    return []
  }, [expression, variables, lastBlock, openBrackets])

  return (
    <div style={{ marginBottom: '1rem' }}>
      <TooltipLabel label={label}>
        <div className={condition}>
          <Callout
            className={expressionArea}
            icon={null}
            intent={isValid ? 'none' : 'danger'}
          >
            {expression.map((block, index) => (
              <ExpressionBlock
                key={index}
                variables={variables}
                block={block}
                onRemove={() => removeBlock(index)}
                onModify={newBlock => modifyBlock(index, newBlock)}
              />
            ))}
            <div className={nextBlock}>
              <HTMLSelect
                options={[DEFAULT_OPTION, ...options]}
                value={option.value}
                onChange={event => {
                  const selectedOption = event.currentTarget.selectedOptions[0]
                  addBlock({
                    value: Number(selectedOption.value),
                    label: selectedOption.label,
                  })
                }}
              />
            </div>
          </Callout>
          <Button
            onClick={clearExpression}
            disabled={expression.length === 0}
            icon='trash'
            text='Clear'
          />
        </div>
        {error && (
          <div className={errorMessage}>
            <Icon
              icon='error'
              intent='danger'
              style={{
                marginRight: '0.5rem',
              }}
            />
            {error}
          </div>
        )}
      </TooltipLabel>
    </div>
  )
}

export default memo(ExpressionBuilder)
