import React, {
  cloneElement,
  ComponentPropsWithRef,
  ReactElement,
  ReactNode,
  useCallback,
  useId,
  useRef,
} from 'react';
import clsx from 'clsx';
import {mergeProps} from '@react-aria/utils';
import {toast} from '@common/ui/toast/toast';
import {Field} from '@common/ui/forms/input-field/field';
import {
  getInputFieldClassNames,
  InputFieldStyle,
} from '@common/ui/forms/input-field/get-input-field-class-names';
import {FileEntry} from '@common/uploads/file-entry';
import {useAutoFocus} from '@common/ui/focus/use-auto-focus';
import {UploadStrategyConfig} from '@common/uploads/uploader/strategy/upload-strategy';
import {useActiveUpload} from '@common/uploads/uploader/use-active-upload';
import {Disk} from '@common/uploads/types/backend-metadata';
import {Button} from '@common/ui/buttons/button';
import {Trans} from '@common/i18n/trans';
import {ProgressBar} from '@common/ui/progress/progress-bar';
import {Input} from '@common/ui/forms/input-field/input';
import {useController} from 'react-hook-form';
import {useFileEntryModel} from '@common/uploads/requests/use-file-entry-model';
import {Skeleton} from '@common/ui/skeleton/skeleton';
import {AnimatePresence, m} from 'framer-motion';
import {opacityAnimation} from '@common/ui/animation/opacity-animation';
import {validateUpload} from '@common/uploads/uploader/validate-upload';
import {UploadedFile} from '@common/uploads/uploaded-file';

interface Props {
  className?: string;
  label?: ReactNode;
  description?: ReactNode;
  invalid?: boolean;
  errorMessage?: ReactNode;
  required?: boolean;
  disabled?: boolean;
  value?: string;
  onChange?: (newValue: string) => void;
  allowedFileTypes?: string[];
  maxFileSize?: number;
  diskPrefix: string;
  disk?: Disk;
  showRemoveButton?: boolean;
  autoFocus?: boolean;
}
export function FileEntryField({
  className,
  label,
  description,
  value,
  onChange,
  diskPrefix,
  disk = Disk.uploads,
  showRemoveButton,
  invalid,
  errorMessage,
  required,
  autoFocus,
  disabled,
  allowedFileTypes,
  maxFileSize,
}: Props) {
  const {
    uploadFile,
    entry,
    uploadStatus,
    deleteEntry,
    isDeletingEntry,
    percentage,
  } = useActiveUpload();

  const inputRef = useRef<HTMLInputElement>(null);

  useAutoFocus({autoFocus}, inputRef);
  const {data} = useFileEntryModel(value, {enabled: !entry && !!value});

  const fieldId = useId();
  const labelId = label ? `${fieldId}-label` : undefined;
  const descriptionId = description ? `${fieldId}-description` : undefined;

  const currentValue = value || entry?.url;
  const currentEntry = entry || data?.fileEntry;

  const uploadOptions: UploadStrategyConfig = {
    showToastOnRestrictionFail: true,
    restrictions: {
      allowedFileTypes,
      maxFileSize,
    },
    metadata: {
      diskPrefix,
      disk,
    },
    onSuccess: (entry: FileEntry) => onChange?.(entry.url),
    onError: message => {
      if (message) {
        toast.danger(message);
      }
    },
  };

  const inputFieldClassNames = getInputFieldClassNames({
    description,
    descriptionPosition: 'top',
    invalid,
    disabled: disabled || uploadStatus === 'inProgress',
  });

  const removeButton = showRemoveButton ? (
    <Button
      variant="link"
      color="danger"
      size="xs"
      disabled={isDeletingEntry || !currentValue || disabled}
      onClick={() => {
        deleteEntry({
          onSuccess: () => onChange?.(''),
        });
      }}
    >
      <Trans message="Remove file" />
    </Button>
  ) : null;

  const handleUpload = useCallback(() => {
    inputRef.current?.click();
  }, []);

  return (
    <div className={clsx('text-sm', className)}>
      {label && (
        <div className="flex items-center justify-between gap-24">
          <div id={labelId} className={inputFieldClassNames.label}>
            {label}
          </div>
          {removeButton}
        </div>
      )}
      {description && (
        <div className={inputFieldClassNames.description}>{description}</div>
      )}
      <div aria-labelledby={labelId} aria-describedby={descriptionId}>
        <Field
          fieldClassNames={inputFieldClassNames}
          errorMessage={errorMessage}
          invalid={invalid}
        >
          <FileInputField
            inputFieldClassNames={inputFieldClassNames}
            currentValue={currentValue}
            currentEntry={currentEntry}
            handleUpload={handleUpload}
          >
            <input
              ref={inputRef}
              aria-labelledby={labelId}
              aria-describedby={descriptionId}
              // if file is already uploaded (from form or via props) set
              // required to false, otherwise farm validation will always fail
              required={currentValue ? false : required}
              accept={allowedFileTypes?.join(',')}
              type="file"
              disabled={uploadStatus === 'inProgress'}
              className="sr-only"
              onChange={e => {
                if (e.target.files?.length) {
                  // "uploadFile" will validate, but need to validate here as well
                  // because there's no easy way to listen for errors using "uploadFile"
                  const errorMessage = validateUpload(
                    new UploadedFile(e.target.files[0]),
                    uploadOptions.restrictions
                  );
                  if (errorMessage && inputRef.current) {
                    inputRef.current.value = '';
                    toast.danger(errorMessage);
                  } else {
                    uploadFile(e.target.files[0], uploadOptions);
                  }
                }
              }}
            />
          </FileInputField>
          {uploadStatus === 'inProgress' && (
            <ProgressBar
              className="absolute left-0 right-0 top-0"
              size="xs"
              value={percentage}
            />
          )}
        </Field>
      </div>
    </div>
  );
}

interface FileInputFieldProps {
  children: ReactElement<ComponentPropsWithRef<'input'>>;
  inputFieldClassNames: InputFieldStyle;
  currentValue?: string;
  currentEntry?: FileEntry;
  handleUpload: () => void;
}
function FileInputField({
  children,
  inputFieldClassNames,
  currentValue,
  currentEntry,
  handleUpload,
}: FileInputFieldProps) {
  const buttonRef = useRef<HTMLButtonElement>(null);
  if (currentValue) {
    return (
      <Field
        wrapperProps={{
          onClick: () => {
            buttonRef.current?.focus();
            buttonRef.current?.click();
          },
        }}
        fieldClassNames={inputFieldClassNames}
      >
        <Input className={clsx(inputFieldClassNames.input, 'gap-10')}>
          <button
            ref={buttonRef}
            type="button"
            className="flex-shrink-0 rounded bg-primary px-10 py-2 text-sm font-semibold text-on-primary outline-none"
            onClick={() => handleUpload()}
          >
            <Trans message="Replace file" />
          </button>
          <AnimatePresence initial={false} mode="wait">
            <div className="min-w-0 overflow-hidden overflow-ellipsis whitespace-nowrap">
              {currentEntry ? (
                <m.div key="file-entry-name" {...opacityAnimation}>
                  {currentEntry.name}
                </m.div>
              ) : (
                <m.div key="skeleton" {...opacityAnimation}>
                  <Skeleton className="min-w-144" />
                </m.div>
              )}
            </div>
          </AnimatePresence>
          {children}
        </Input>
      </Field>
    );
  }

  return cloneElement(children, {
    className: clsx(
      inputFieldClassNames.input,
      'py-8',
      'file:bg-primary file:text-on-primary file:border-none file:rounded file:text-sm file:font-semibold file:px-10 file:h-24 file:mr-10'
    ),
  });
}

interface FormFileEntryFieldProps extends Props {
  name: string;
}
export function FormFileEntryField(props: FormFileEntryFieldProps) {
  const {
    field: {onChange, value = null},
    fieldState: {error},
  } = useController({
    name: props.name,
  });

  const formProps: Partial<Props> = {
    onChange,
    value,
    invalid: error != null,
    errorMessage: error ? <Trans message="Please select a file." /> : null,
  };

  return <FileEntryField {...mergeProps(formProps, props)} />;
}
