import classNames from 'classnames'
import {
  ChangeEvent,
  DragEvent,
  forwardRef,
  InputHTMLAttributes,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react'

import { PktIcon } from '..'
import { uiMultipleTexts, uiSingleTexts, uiThumbnailMultipleTexts, uiThumbnailSingleTexts } from './texts'
import { FileItem, TFileItemList, TUploadStrategy } from './types'

const DEFAULT_FORMATS_HELP_TEXT = '.PDF, .JPEG, .JPG, .PNG, .HEIC, .DOC, .DOCX, .ODT'

/**
 * Props for the internal `DropZone` building block.
 *
 * This component is responsible for:
 * - rendering the native `<input type="file">` (visually hidden, still accessible)
 * - handling drag & drop + file dialog selection
 * - optionally rendering hidden inputs with file IDs when `uploadStrategy="custom"`
 */
interface IDropZoneProps
  extends Omit<InputHTMLAttributes<HTMLInputElement>, 'checked' | 'size' | 'type' | 'value' | 'onChange' | 'width'> {
  /** Called with `FileItem`s created from dropped/selected files. */
  onFilesAdded?: (files: TFileItemList) => void
  /** Used for the native input `id` and related label/ARIA wiring. */
  id: string
  /** Allow selecting/dropping multiple files. */
  multiple?: boolean
  /** Current file list (used for custom hidden inputs + re-populating the native input). */
  value: TFileItemList
  /**
   * Field name.
   * - `uploadStrategy="form"`: used as `<input name="...">` so the files are posted on submit.
   * - `uploadStrategy="custom"`: used for hidden `<input type="hidden" name="...">` file IDs.
   */
  name: string
  /** Upload mode. */
  uploadStrategy: TUploadStrategy
  /** Enables alternate texts/UX for thumbnail view. */
  isThumbnailView?: boolean
  /** Disables all interaction. */
  disabled?: boolean
  /** IDs for screen reader announcements (uploaded/errors). */
  srAnnouncementIds?: {
    uploaded: string
    errors: string
  }
  /** Whether the file input already has an external visible label associated with it. */
  hasVisibleLabel?: boolean
}
export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
  (
    {
      id,
      multiple,
      value,
      onFilesAdded = () => {},
      name,
      uploadStrategy,
      accept,
      isThumbnailView = false,
      disabled = false,
      srAnnouncementIds,
      hasVisibleLabel = false,
      ...inputProps
    }: IDropZoneProps,
    forwardedRef,
  ) => {
    const fileInputRef = useRef<HTMLInputElement>(null)

    const resolvedAccept = typeof accept === 'string' && accept.trim().length > 0 ? accept : undefined
    const acceptedFormatsReadableString = useMemo(
      () =>
        resolvedAccept
          ? resolvedAccept
              .split(/\s*,\s*/)
              .map((format) => format.trim())
              .filter(Boolean)
              .join(', ')
              .toUpperCase()
          : DEFAULT_FORMATS_HELP_TEXT,
      [resolvedAccept],
    )

    useImperativeHandle(forwardedRef, () => fileInputRef.current! as HTMLInputElement)

    const [isDragActive, setIsDragActive] = useState(false)

    const uiTexts: typeof uiMultipleTexts = useMemo(() => {
      if (isThumbnailView) {
        return multiple ? uiThumbnailMultipleTexts : uiThumbnailSingleTexts
      }
      return multiple ? uiMultipleTexts : uiSingleTexts
    }, [multiple, isThumbnailView])

    const populateNativeFileInput = useCallback(
      (fileItemList: TFileItemList) => {
        if (!fileInputRef.current) return
        try {
          fileInputRef.current.files = createFileList(fileItemList)
        } catch {
          // Setting files property may fail in test environments like jsdom
          // This is not critical for the component's functionality
        }
      },
      [fileInputRef.current, value],
    )

    useEffect(() => {
      populateNativeFileInput(value)
    }, [fileInputRef, value])
    const filesAdded = useCallback(
      (files: Array<File>) => {
        const addedFileItems = files.map((file) => new FileItem(file))
        onFilesAdded(addedFileItems)
        // Clear the input value to allow uploading the same file again if needed
        if (fileInputRef.current) {
          fileInputRef.current.value = ''
        }
      },
      [onFilesAdded],
    )

    const filesSelectedInDialog = (event: ChangeEvent<HTMLInputElement>) => {
      const selectedFiles = (event.target as HTMLInputElement).files!
      const userCancelledFileSelectionDialog = selectedFiles.length === 0
      if (userCancelledFileSelectionDialog) {
        if (multiple) {
          populateNativeFileInput(value)
        } else {
          // onFilesChanged([]) // TODO: Nullstill ved avbryt i enkel opplasting?
        }
        return
      }
      filesAdded(Array.from(selectedFiles))
    }

    const filesDroppedOnDropzone = (files: Array<File>) => {
      filesAdded(files)
    }

    const handleDrop = (event: DragEvent<HTMLDivElement>) => {
      event.preventDefault()
      setIsDragActive(false)
      if (disabled) return
      const droppedFiles = event.dataTransfer.files
      const arr: Array<File> = Array.from(droppedFiles)
      filesDroppedOnDropzone(arr)
    }

    const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
      event.preventDefault()
      if (disabled) return
      setIsDragActive(true)
    }

    const handleDragLeave = () => {
      setIsDragActive(false)
    }

    const handleDropZoneClick = useCallback(
      (event: React.MouseEvent<HTMLDivElement>) => {
        if (disabled) return
        const target = event.target as HTMLElement
        // ignore clicks on the explicit open button (it opens the dialog itself)
        if (target.closest('.pkt-fileupload__drop-zone__placeholder__title__open-file-dialog')) return
        fileInputRef.current?.click()
      },
      [disabled],
    )

    return (
      <div
        onDrop={handleDrop}
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        onClick={handleDropZoneClick}
        className={classNames('pkt-fileupload__drop-zone', {
          'pkt-fileupload__drop-zone--drag-active': isDragActive,
          'pkt-fileupload__drop-zone--disabled': disabled,
        })}
      >
        <input
          {...inputProps}
          id={id}
          type={'file'}
          ref={fileInputRef}
          multiple={multiple}
          onChange={filesSelectedInDialog}
          accept={resolvedAccept}
          disabled={disabled}
          name={(uploadStrategy === 'form' && name) || undefined} // Ikke sett name hvis uploadStrategy er 'custom' - ignorerer ved POST
          aria-label={hasVisibleLabel ? undefined : multiple ? 'Velg filer' : 'Velg fil'}
        />
        {uploadStrategy === 'custom' && (
          <>
            {value?.map((fileItem) => (
              <input key={fileItem.fileId} type="hidden" name={name} value={fileItem.fileId} />
            ))}
          </>
        )}
        <div className="pkt-fileupload__drop-zone__placeholder">
          <PktIcon name={'attachment'} className="pkt-fileupload__drop-zone__placeholder__icon" aria-hidden="true" />
          <p className={'pkt-fileupload__drop-zone__placeholder__title'}>
            {isDragActive ? (
              `${uiTexts.dropFilesHere} ...`
            ) : (
              <>
                {uiTexts.selectOrDragFiles}{' '}
                <button
                  className="pkt-fileupload__drop-zone__placeholder__title__open-file-dialog"
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    fileInputRef.current?.click()
                  }}
                  type="button"
                >
                  {uiTexts.chooseFiles}
                </button>
              </>
            )}
          </p>
          <p className={'pkt-fileupload__drop-zone__placeholder__formats'}>Format: {acceptedFormatsReadableString}</p>
        </div>
      </div>
    )
  },
)

/**
 * Create a `FileList` from a list of `FileItem`s.
 *
 * Note: relies on `DataTransfer`, which may be missing or restricted in test environments (jsdom).
 */
export const createFileList = (fileItemList: TFileItemList) => {
  const dataTransfer = new DataTransfer()
  for (const fileItem of fileItemList) {
    dataTransfer.items.add(fileItem.file)
  }
  return dataTransfer.files
}
