import { ChangeEvent, DragEvent, useRef } from 'react'

import { omitThemingProps, styled, useStyleConfig, vui } from '../core'
import Icon, { IconProp } from '../icon'
import T from '../t'
import { cs } from '../utils'
import { DragAndDropProps } from './dragAndDrop.types'

const defaultIcon: IconProp = 'falCloudUpload'

const defaultText = 'Click or drag files to this area'

const inputType = 'file'

const DragAndDropBase = styled.labelBox`
  display: flex;
  min-width: 0;
  transition-duration: normal;
  justify-content: center;
`

/** Prepares uploading files by selecting or dragging. */
export const DragAndDrop = vui<'label', DragAndDropProps>((props, ref) => {
  const {
    accept,
    className,
    icon = defaultIcon,
    selectFolderOnClick,
    selectFolderOnDrop,
    onFilesAdded,
    multiple = true,
    text = defaultText,
    children,
    disabled,
    ...rest
  } = omitThemingProps(props)
  const fileInput = useRef<HTMLInputElement>(null)
  const styles = useStyleConfig('DragAndDrop', props)

  const processFiles = (files: FileList): File[] => Array.from(files).map(file => file)

  const handleFileInput = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault()
    if (disabled) return undefined
    const files = processFiles(e?.currentTarget?.files as FileList)
    if (files?.length) {
      onFilesAdded?.(files)
      // Reseting the input type to allow selecting the same file(s)
      e.currentTarget.type = ''
      e.currentTarget.type = inputType
    }
  }

  const getFilesWebkitDataTransferItems = (dataTransferItems: DataTransferItemList): Promise<File[]> => {
    const traverseFileTreePromise = (item: FileSystemEntry, path: string = ''): Promise<File[]> =>
      new Promise(resolve => {
        if (item.isFile) {
          const fileEntry = item as FileSystemFileEntry
          fileEntry.file(file => {
            ;(file as any).filepath = path + file.name // Save full path with type casting
            files.push(file)
            resolve([file])
          })
        } else if (item.isDirectory) {
          const dirEntry = item as FileSystemDirectoryEntry
          const dirReader = dirEntry.createReader()
          dirReader.readEntries(entries => {
            const entriesPromises: Promise<File[]>[] = entries.map(entry =>
              traverseFileTreePromise(entry, path + dirEntry.name + '/'),
            )
            resolve(Promise.all(entriesPromises).then(entries => [].concat(...(entries as any[]))))
          })
        }
      })

    const files: File[] = []
    return new Promise((resolve, reject) => {
      const entriesPromises: Promise<File[]>[] = []
      for (let i = 0; i < dataTransferItems.length; i++) {
        const item = dataTransferItems[i].webkitGetAsEntry()
        if (item) entriesPromises.push(traverseFileTreePromise(item))
      }
      Promise.all(entriesPromises)
        .then(() => resolve(files))
        .catch(reject)
    })
  }

  const handleDrop = (e: DragEvent<HTMLElement>) => {
    e.preventDefault()
    if (disabled) return undefined

    if (selectFolderOnDrop) {
      const items = e.dataTransfer.items
      getFilesWebkitDataTransferItems(items).then(files => files?.length && onFilesAdded?.(files))
      return undefined
    }

    const files = e.dataTransfer?.files ? processFiles(e.dataTransfer.files) : []
    if (files?.length) onFilesAdded?.(files)
  }

  return (
    <DragAndDropBase
      className={cs('vui-drag-and-drop', className)}
      cursor={disabled ? 'not-allowed' : 'pointer'}
      onDragEnter={e => e.preventDefault()}
      onDragOver={e => e.preventDefault()}
      onDrop={e => handleDrop(e)}
      opacity={disabled ? 0.5 : 1}
      p={5}
      pointerEvents={disabled ? 'none' : 'auto'}
      ref={ref}
      {...styles}
      {...rest}
    >
      {children || (
        <T display="flex">
          <Icon mr={1} name={icon} />
          {text}
        </T>
      )}
      <input
        accept={accept}
        className="vui-drag-and-drop-hidden-input"
        multiple={multiple}
        onChange={handleFileInput}
        ref={fileInput}
        style={{ display: 'none' }}
        type={inputType}
        {...(selectFolderOnClick && { webkitdirectory: 'true', directory: 'true' })}
      />
    </DragAndDropBase>
  )
})

DragAndDrop.displayName = 'DragAndDrop'
export default DragAndDrop
