import getStudio from '@theatre/studio/getStudio'
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
import useDrag from '@theatre/studio/uiComponents/useDrag'
import useKeyDown from '@theatre/studio/uiComponents/useKeyDown'
import useValToAtom from '@theatre/studio/uiComponents/useValToAtom'
import mutableSetDeep from '@theatre/shared/utils/mutableSetDeep'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import {usePrism} from '@theatre/react'
import type {$IntentionalAny} from '@theatre/shared/utils/types'
import type {Pointer} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
import React, {useMemo, useRef} from 'react'
import styled from 'styled-components'
import type {
  DopeSheetSelection,
  SequenceEditorPanelLayout,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import type {SequenceEditorTree_AllRowTypes} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'

const Container = styled.div<{isShiftDown: boolean}>`
  cursor: ${(props) => (props.isShiftDown ? 'cell' : 'default')};
`

const DopeSheetSelectionView: React.FC<{
  layoutP: Pointer<SequenceEditorPanelLayout>
}> = ({layoutP, children}) => {
  const [containerRef, containerNode] = useRefAndState<HTMLDivElement | null>(
    null,
  )
  const isShiftDown = useKeyDown('Shift')
  const selectionBounds = useCaptureSelection(layoutP, containerNode)
  const selectionBoundsRef = useRef<typeof selectionBounds>(selectionBounds)
  selectionBoundsRef.current = selectionBounds

  return (
    <Container ref={containerRef} isShiftDown={isShiftDown}>
      {selectionBounds && (
        <SelectionRectangle state={selectionBounds} layoutP={layoutP} />
      )}
      {children}
    </Container>
  )
}

type SelectionBounds = {
  positions: [from: number, to: number]
  ys: [from: number, to: number]
}

function useCaptureSelection(
  layoutP: Pointer<SequenceEditorPanelLayout>,
  containerNode: HTMLDivElement | null,
) {
  const [ref, state] = useRefAndState<SelectionBounds | null>(null)

  useDrag(
    containerNode,
    useMemo((): Parameters<typeof useDrag>[1] => {
      return {
        dontBlockMouseDown: true,
        lockCursorTo: 'cell',
        onDragStart(event) {
          if (!event.shiftKey || event.target instanceof HTMLInputElement) {
            return false
          }
          const rect = containerNode!.getBoundingClientRect()

          const posInScaledSpace = event.clientX - rect.left

          const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)(
            posInScaledSpace,
          )

          ref.current = {
            positions: [posInUnitSpace, posInUnitSpace],
            ys: [event.clientY - rect.top, event.clientY - rect.top],
          }

          val(layoutP.selectionAtom).setState({current: undefined})
        },
        onDrag(dx, dy, event) {
          const state = ref.current!
          const rect = containerNode!.getBoundingClientRect()

          const posInScaledSpace = event.clientX - rect.left

          const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)(
            posInScaledSpace,
          )

          ref.current = {
            positions: [ref.current!.positions[0], posInUnitSpace],
            ys: [ref.current!.ys[0], event.clientY - rect.top],
          }

          const selection = utils.boundsToSelection(layoutP, ref.current)
          val(layoutP.selectionAtom).setState({current: selection})
        },
        onDragEnd(dragHappened) {
          ref.current = null
        },
      }
    }, [layoutP, containerNode, ref]),
  )

  // useEffect(() => {
  //   if (!containerNode) return
  //   const onClick = () => {

  //   }
  //   containerNode.addEventListener('click', onClick)

  //   return () => {
  //     containerNode.removeEventListener('click', onClick)
  //   }
  // }, [containerNode])

  return state
}

namespace utils {
  const collectorByLeafType: {
    [K in SequenceEditorTree_AllRowTypes['type']]?: (
      layoutP: Pointer<SequenceEditorPanelLayout>,
      leaf: Extract<SequenceEditorTree_AllRowTypes, {type: K}>,
      bounds: Exclude<SelectionBounds, null>,
      selection: DopeSheetSelection,
    ) => void
  } = {
    primitiveProp(layoutP, leaf, bounds, selection) {
      const {sheetObject, trackId} = leaf
      const trackData = val(
        getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId]
          .sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[
          sheetObject.address.objectKey
        ].trackData[trackId],
      )!
      const toCollect = trackData!.keyframes.filter(
        (kf) =>
          kf.position >= bounds.positions[0] &&
          kf.position <= bounds.positions[1],
      )

      for (const kf of trackData.keyframes) {
        if (kf.position <= bounds.positions[0]) continue
        if (kf.position >= bounds.positions[1]) break

        mutableSetDeep(
          selection,
          (p) =>
            p.byObjectKey[sheetObject.address.objectKey].byTrackId[trackId]
              .byKeyframeId[kf.id],
          true,
        )
      }
    },
  }

  const collectChildren = (
    layoutP: Pointer<SequenceEditorPanelLayout>,
    leaf: SequenceEditorTree_AllRowTypes,
    bounds: Exclude<SelectionBounds, null>,
    selection: DopeSheetSelection,
  ) => {
    if ('children' in leaf) {
      for (const sub of leaf.children) {
        collectFromAnyLeaf(layoutP, sub, bounds, selection)
      }
    }
  }

  function collectFromAnyLeaf(
    layoutP: Pointer<SequenceEditorPanelLayout>,
    leaf: SequenceEditorTree_AllRowTypes,
    bounds: Exclude<SelectionBounds, null>,
    selection: DopeSheetSelection,
  ) {
    if (
      bounds.ys[0] > leaf.top + leaf.heightIncludingChildren ||
      leaf.top > bounds.ys[1]
    ) {
      return
    }
    const collector = collectorByLeafType[leaf.type]
    if (collector) {
      collector(layoutP, leaf as $IntentionalAny, bounds, selection)
    } else {
      collectChildren(layoutP, leaf, bounds, selection)
    }
  }

  export function boundsToSelection(
    layoutP: Pointer<SequenceEditorPanelLayout>,
    bounds: Exclude<SelectionBounds, null>,
  ): DopeSheetSelection {
    const sheet = val(layoutP.tree.sheet)
    const selection: DopeSheetSelection = {
      type: 'DopeSheetSelection',
      byObjectKey: {},
      getDragHandlers(origin) {
        let tempTransaction: CommitOrDiscard | undefined

        let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
        return {
          onDragStart() {
            toUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
          },
          onDrag(dx, _, event) {
            let delta = toUnitSpace(dx)
            if (tempTransaction) {
              tempTransaction.discard()
              tempTransaction = undefined
            }

            const snapTarget = event
              .composedPath()
              .find(
                (el): el is Element =>
                  el instanceof Element &&
                  el !== origin.domNode &&
                  el.hasAttribute('data-pos'),
              )

            if (snapTarget) {
              const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
              if (isFinite(snapPos)) {
                delta = snapPos - origin.positionAtStartOfDrag
              }
            }

            tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
              const transformKeyframes =
                stateEditors.coreByProject.historic.sheetsById.sequence
                  .transformKeyframes

              for (const objectKey of Object.keys(selection.byObjectKey)) {
                const {byTrackId} = selection.byObjectKey[objectKey]!
                for (const trackId of Object.keys(byTrackId)) {
                  const {byKeyframeId} = byTrackId[trackId]!
                  transformKeyframes({
                    trackId,
                    keyframeIds: Object.keys(byKeyframeId),
                    translate: delta,
                    scale: 1,
                    origin: 0,
                    snappingFunction: sheet.getSequence().closestGridPosition,
                    objectKey,
                    projectId: origin.projectId,
                    sheetId: origin.sheetId,
                  })
                }
              }
            })
          },
          onDragEnd(dragHappened) {
            if (dragHappened) {
              if (tempTransaction) {
                tempTransaction.commit()
              }
            } else {
              if (tempTransaction) {
                tempTransaction.discard()
              }
            }
            tempTransaction = undefined
          },
        }
      },
      delete() {
        getStudio()!.transaction(({stateEditors}) => {
          const deleteKeyframes =
            stateEditors.coreByProject.historic.sheetsById.sequence
              .deleteKeyframes

          for (const objectKey of Object.keys(selection.byObjectKey)) {
            const {byTrackId} = selection.byObjectKey[objectKey]!
            for (const trackId of Object.keys(byTrackId)) {
              const {byKeyframeId} = byTrackId[trackId]!
              deleteKeyframes({
                ...sheet.address,
                objectKey,
                trackId,
                keyframeIds: Object.keys(byKeyframeId),
              })
            }
          }
        })
      },
    }

    bounds = sortBounds(bounds)

    const tree = val(layoutP.tree)
    collectFromAnyLeaf(layoutP, tree, bounds, selection)

    return selection
  }
}

const SelectionRectangleDiv = styled.div`
  position: absolute;
  background: rgba(255, 255, 255, 0.1);
  border: 1px dashed rgba(255, 255, 255, 0.4);
  box-size: border-box;
`

const sortBounds = (b: SelectionBounds): SelectionBounds => {
  return {
    positions: [...b.positions].sort(
      (a, b) => a - b,
    ) as SelectionBounds['positions'],
    ys: [...b.ys].sort((a, b) => a - b) as SelectionBounds['ys'],
  }
}

const SelectionRectangle: React.FC<{
  state: Exclude<SelectionBounds, null>
  layoutP: Pointer<SequenceEditorPanelLayout>
}> = ({state, layoutP}) => {
  const atom = useValToAtom(state)

  return usePrism(() => {
    const state = val(atom.pointer)
    const sorted = sortBounds(state)

    const unitSpaceToScaledSpace = val(layoutP.scaledSpace.fromUnitSpace)

    const positionsInScaledSpace = sorted.positions.map(unitSpaceToScaledSpace)

    const top = sorted.ys[0]
    const height = sorted.ys[1] - sorted.ys[0]

    const left = positionsInScaledSpace[0]
    const width = positionsInScaledSpace[1] - positionsInScaledSpace[0]

    return (
      <SelectionRectangleDiv
        style={{
          top: top + 'px',
          height: height + 'px',
          left: left + 'px',
          width: width + 'px',
        }}
      ></SelectionRectangleDiv>
    )
  }, [layoutP, atom])
}

export default DopeSheetSelectionView
