import React, { useEffect, useRef, useState } from 'react'

import styles from './_file-uploader.module.scss'
import Button from '../Button/Button'
import FormLabel from '../FormLabel/FormLabel'
import { type FormLabelProps } from '../FormLabel/FormLabel'
import Icon from '../Icons/Icon'
import ProgressBar from '../ProgressBar/ProgressBar'
import { toast } from '../Toast/Toast'
import TrimText from '../TrimText/TrimText'
import { Virtual } from '../Virtual/Virtual'
import { c } from '../../translations/LibraryTranslationService'
import ScrollingContainer from '../ScrollingContainer/ScrollingContainer'

export enum MIMETypes {
  Images = 'image/jpeg,image/png,image/gif,image/svg+xml,image/webp',
  Audio = 'audio/mpeg,audio/wav',
  Videos = 'video/mp4,video/x-msvideo,video/quicktime,video/x-ms-wmv,video/x-matroska,video/x-flv,video/webm,video/mpeg,video/3gpp,video/3gpp2,video/ogg,video/x-m4v',
  Media = `${MIMETypes.Images},${MIMETypes.Audio},${MIMETypes.Videos}`,
  Documents = 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation',
  Text = 'text/plain',
  Archives = 'application/zip,application/x-rar-compressed',
  Spreadsheets = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,text/csv,application/csv',
}

type FileTypeWithStatus = File & {
  status: 'uploading' | 'success' | 'error'
  progress?: number
  errorMessage?: string
}

type FileTypeWithoutStatus = File & {
  status?: never
  progress?: never
}

export type FileType = FileTypeWithStatus | FileTypeWithoutStatus

type FileUploaderBaseProps = {
  /** Function to save uploaded files */
  uploadCallout: (files: FileType[]) => void
  /** Allow FileUploader to receive multiple files */
  acceptMultiple?: boolean
  /** Optionally restrict the types of files that are accepted. If your required type is not specified in the enum MIMETypes, you can add in the necessary strings to the array. */
  acceptedFileTypes?: Array<MIMETypes | string>
  /** Optionally make the component disabled */
  disabled?: boolean
  /** Optionally hide the x button */
  noFileRemoval?: boolean
  /** Optional prop to add a test id to the FileUploader for QA testing */
  qaTestId?: string
}

type WithControlled = FileUploaderBaseProps & {
  // TODO: We want all implementations to have the controlled and files props. For now, we are handling these as optional props so we do not break existing implementations.
  /** Optionally allow the files to be in a controlled state */
  controlled: true
  /** Files to upload */
  files: FileType[]
  /** Function to remove files */
  removeFileCallout: (file: FileType) => void
}

type WithoutControlled = FileUploaderBaseProps & {
  /** Files to upload */
  files?: FileType[]
  controlled?: never
  removeFileCallout?: never
}

type DetermineAsyncType = WithControlled | WithoutControlled

type WithFormLabelProps = FileUploaderBaseProps &
  DetermineAsyncType & {
    /** FormLabel component above the upload field */
    formLabelProps: Omit<FormLabelProps, 'rightLabel'>
    /** Optional download link callout */
    downloadCallout?: () => void
  }

type WithoutFormLabelProps = FileUploaderBaseProps &
  DetermineAsyncType & {
    formLabelProps?: never
    downloadCallout?: never
  }

export type FileUploaderProps = WithFormLabelProps | WithoutFormLabelProps

const FileUploader = ({
  uploadCallout,
  files = [],
  formLabelProps,
  downloadCallout,
  acceptedFileTypes,
  disabled = false,
  acceptMultiple = false,
  controlled,
  removeFileCallout,
  noFileRemoval = false,
  qaTestId = 'file-uploader',
}: FileUploaderProps): React.JSX.Element => {
  const [uploadedFiles, setUploadedFiles] = useState<FileType[]>(files)
  const [showError, setShowError] = useState<boolean>(false)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const [buttonText, setButtonText] = useState<string>(
    c('chooseFile', {
      count: acceptMultiple ? 2 : 1,
    }),
  )

  const upload = (files: FileList | File[] | null) => {
    if (disabled) return // Prevent upload if disabled
    if (!files || files.length === 0) return

    const newFiles = Array.isArray(files) ? files : Array.from(files)
    const acceptedFiles = acceptMultiple ? newFiles : [newFiles[0]] // Only take the first file if not accepting multiple

    const validFiles = acceptedFiles.filter((file) => {
      const isAcceptedType = acceptedFileTypes
        ? acceptedFileTypes.some((type) => {
            const mimeTypes = type.split(',') // Split the enum string into an array
            return mimeTypes.includes(file.type)
          })
        : true

      if (!isAcceptedType) {
        setShowError(true)
      }
      return isAcceptedType
    })

    if (validFiles.length > 0) {
      !controlled
        ? setUploadedFiles((prevFiles) => {
            const updatedFiles = acceptMultiple
              ? [...prevFiles, ...validFiles]
              : validFiles
            uploadCallout(updatedFiles)
            return updatedFiles
          })
        : uploadCallout(validFiles)
      setShowError(false)
    }
  }

  const handleUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
    event.preventDefault()
    event.stopPropagation()
    upload(event.target.files)
    event.target.value = ''
  }

  const handleDrop = (evt: React.DragEvent) => {
    evt.preventDefault()
    evt.stopPropagation()

    if (evt.dataTransfer.items && evt.dataTransfer.items.length > 0) {
      const processEntry = async (entry: any): Promise<File[]> => {
        return new Promise((resolve) => {
          if (entry.isFile) {
            // If it's a file, get it and add it to our files list
            entry.file((file: File) => {
              resolve([file])
            })
          } else if (entry.isDirectory) {
            // If the toggle is on and we have a directory, process it
            const dirReader = entry.createReader()
            const allFiles: File[] = []

            // Define a recursive function to read all directory contents
            const readEntries = () => {
              dirReader.readEntries(async (entries: any[]) => {
                if (entries.length === 0) {
                  // We've read all entries in this directory
                  resolve(allFiles)
                } else {
                  // Process each entry
                  const filePromises = entries.map(processEntry)
                  const files = await Promise.all(filePromises)
                  allFiles.push(...files.flat())

                  // Continue reading
                  readEntries()
                }
              })
            }

            // Start reading directory contents
            readEntries()
          } else {
            resolve([])
          }
        })
      }

      const processDroppedItems = async () => {
        try {
          // Process all items in parallel to handle multiple folders efficiently
          const filePromises = Array.from(evt.dataTransfer.items).map(
            (item) => {
              const entry = item.webkitGetAsEntry?.()
              if (entry) {
                return processEntry(entry)
              } else if (item.getAsFile) {
                // Fallback for browsers not supporting webkitGetAsEntry
                const file = item.getAsFile()
                return file ? Promise.resolve([file]) : Promise.resolve([])
              }
              return Promise.resolve([])
            },
          )

          // Wait for all files to be processed from all folders
          const fileArrays = await Promise.all(filePromises)
          const allFiles = fileArrays.flat()

          if (allFiles.length > 0) {
            if (!acceptMultiple && allFiles.length > 1) {
              // If not accepting multiple files, just use the first file
              upload([allFiles[0]])
            } else {
              upload(allFiles)
            }
          }
        } catch {
          toast({
            type: 'error',
            message: c('errorProcessingDroppedFiles'),
          })
        }
      }

      processDroppedItems()
    } else {
      // Regular file upload handling
      upload(evt.dataTransfer.files)
    }

    evt.dataTransfer.clearData()
  }

  const removeFile = (index: number) => {
    !controlled
      ? setUploadedFiles((prevFiles) => {
          const updatedFiles = prevFiles.filter((_, i) => i !== index)

          uploadCallout(updatedFiles)
          return updatedFiles
        })
      : removeFileCallout?.(files[index])
  }

  const displayedFiles = controlled ? files : uploadedFiles

  useEffect(() => {
    if (!acceptMultiple) {
      const buttonText =
        displayedFiles.length > 0
          ? c('changeFile')
          : c('chooseFile', {
              count: 1,
            })
      setButtonText(buttonText)
    }
  }, [displayedFiles.length, acceptMultiple])

  const acceptedFilesString = acceptedFileTypes
    ?.map((type) =>
      type
        .split(',')
        .map((t) => t.trim())
        .join(', '),
    )
    .join(', ')

  const virtualRowHeight = 32 // Height of each row in the virtualized list. Each row needs 25px of height and 8px of padding

  return (
    <div qa-test-id={qaTestId}>
      {formLabelProps ? (
        <FormLabel
          {...formLabelProps}
          {...(downloadCallout
            ? {
                rightLabel: (
                  <Button
                    styleType='text-blue'
                    onClick={downloadCallout}
                    className={styles.downloadButton}
                  >
                    {c('downloadTemplate')}
                  </Button>
                ),
              }
            : {})}
          {...(showError ? { error: true } : {})}
        />
      ) : null}
      <div
        className={`${styles.fileUpload} ${showError ? styles.error : ''} ${disabled ? styles.disabled : ''}`}
        onDrop={disabled ? undefined : handleDrop}
        onDragOver={
          disabled
            ? undefined
            : (evt) => {
                evt.preventDefault()
                evt.stopPropagation()
              }
        }
      >
        <div>
          {displayedFiles.length >= 1 ? (
            <div className={styles.selectedText}>
              <Icon icon='upload' iconSize='16px' />
              <span>
                {c('numOfFiles', {
                  count: displayedFiles.length,
                })}
              </span>
            </div>
          ) : (
            <div className={styles.unselectedText}>
              <Icon icon='upload' iconSize='16px' />
              <span>
                {c('dragAndDropOrChooseFile', {
                  count: acceptMultiple ? 2 : 1,
                })}
              </span>
            </div>
          )}
        </div>
        <div className={styles.chooseFileInputs}>
          <Button
            as='button'
            type='button' // prevent from submitting the form
            styleType='secondary'
            className={styles.chooseFileButton}
            onClick={() => {
              if (!disabled) {
                fileInputRef?.current?.click()
              }
            }}
            disabled={disabled}
          >
            <input
              hidden
              name='fileToUpload'
              onChange={handleUpload}
              ref={fileInputRef}
              type='file'
              multiple={acceptMultiple}
              disabled={disabled}
              accept={acceptedFilesString}
            />
            {buttonText}
          </Button>
        </div>
      </div>
      {showError && acceptedFileTypes ? (
        <div className={styles.errorText}>
          {c('errorAcceptedFileTypes', {
            acceptedFileTypes: acceptedFilesString,
          })}
        </div>
      ) : null}
      {displayedFiles.length > 0 ? (
        <Virtual count={displayedFiles.length} rowHeight={virtualRowHeight}>
          {({ virtualizer, virtualizedParentRef }) => (
            <ScrollingContainer
              ref={
                virtualizedParentRef as unknown as React.RefObject<HTMLDivElement>
              }
              height={Math.min(
                displayedFiles.length * virtualRowHeight + 32,
                260, // 32px is the combined height of the white gradients. 260px is the max height allowed for the container. We chose 260px because it fits well with the gradient heights and file row heights.
              )}
            >
              <Virtual.List virtualizer={virtualizer}>
                {({ index }) => (
                  <div
                    className={styles.fileRow}
                    key={`${displayedFiles[index].name}-${index}`}
                  >
                    <div style={{ height: 25 }}>
                      <TrimText text={displayedFiles[index].name} limit={30} />
                    </div>
                    {controlled && displayedFiles[index].status ? (
                      <FileStatus
                        status={displayedFiles[index].status}
                        progress={displayedFiles[index].progress}
                        errorMessage={displayedFiles[index].errorMessage}
                      />
                    ) : (
                      <div />
                    )}
                    {!noFileRemoval && (
                      <Button as='unstyled' onClick={() => removeFile(index)}>
                        <Icon
                          icon='x'
                          iconSize='12px'
                          qaTestId={`${qaTestId}-x-icon`}
                        />
                      </Button>
                    )}
                  </div>
                )}
              </Virtual.List>
            </ScrollingContainer>
          )}
        </Virtual>
      ) : null}
    </div>
  )
}

export default FileUploader

type FileStatusProps = {
  status: FileType['status']
  progress?: number
  errorMessage?: string
}

const FileStatus = ({
  status,
  progress = 0,
  errorMessage,
}: FileStatusProps) => {
  switch (status) {
    case 'uploading':
      return <ProgressBar percentage={progress} />
    case 'error':
      return (
        <span className={styles.uploadFailed}>
          <Icon icon='caution' iconSize='12px' color='dark-red' />
          <TrimText text={errorMessage || c('uploadFailed')} />
        </span>
      )
    case 'success':
      return (
        <span className={styles.uploadComplete}>
          <Icon icon='check' iconSize='12px' color='dark-green' />
          {c('uploadComplete')}
        </span>
      )
    default:
      return null
  }
}
