import "./style.pcss";
import type {
  FC,
  HTMLAttributes,
  RefObject,
  ReactElement,
} from "react";
import {
  useEffect,
  isValidElement,
  useRef,
  cloneElement,
  Children,
  useImperativeHandle,
  useMemo,
} from "react";
import type {
  IFilesUploader,
  TInputChangeEvent,
  IOnFilesChangeArg,
  IFilesUploaderRef,
} from "./config/types";
import { FilesUploaderCtx } from "./context/index";
import { getConvertedFileTypes } from "./lib/index";
import { useFilesUploader } from "./lib/useFilesUploader";
import { FilesUploaderList } from "./ui/FilesUploaderList";
import { FilesUploaderText } from "./ui/FilesUploaderText";
import { FilesUploaderTip } from "./ui/FilesUploaderTip";
import type { IComponentBaseProps } from "../../types/components/index";
import { useCN } from "../../utils/react/useCN";
import { useListeners } from "../../utils/react/useListeners";
import { useSSGRender } from "../../utils/react/useSSGRender";
import type { IInputRef } from "../input";
import { Input } from "../input";

/**
 * Компонент загрузчика файлов
 * @returns {ReactElement}
 * */
const FilesUploader: FC<IFilesUploader & IComponentBaseProps> = (props): ReactElement => {

  const {
    baseClass = "filesUploader",
    extraClasses = {},
    extraAttrs = {},
    fileTypes = [ "jpg", "jpeg", "png", "pdf", "doc", "docx", "txt" ],
    maxSizeType = "all",
    name = "files",
    isDisabled = false,
    maxSize = 5,
    onFilesChange = null,
    items = [],
    maxFiles = 1,
    isRequired = false,
    children = null,
    slotFilesList = null,
    slotFileItem = null,
    slotTip = null,
    slotText = null,
    id = null,
    form = null,
    onFileDelete = null,
    onFileRender = null,
    onMount = null,
    onUnmount = null,
    ref,
  } = props;

  const { getCN } = useCN(baseClass);

  const containerRef: RefObject<HTMLDivElement | null> = useRef(null);

  const inputRef: RefObject<HTMLInputElement | null> = useRef(null);

  const hiddenInputRef: RefObject<IInputRef | null> = useRef(null);

  const isMultiple = maxFiles > 1;

  const {
    isLoading,
    isDragging,
    isError,
    handleDragStart,
    handleDragEnter,
    handleDragLeave,
    handleDragOver,
    handleDrop,
    filesMap,
    handleChangeFiles,
    handleFileItemDeleteClick,
    setFilesMap,
  } = useFilesUploader(items, isDisabled, fileTypes, maxSize, maxFiles, hiddenInputRef, inputRef, onFilesChange, onFileDelete, maxSizeType);

  useEffect(() => {
    if (typeof onMount === "function") {
      onMount();
    }

    return () => {
      if (typeof onUnmount === "function") {
        onUnmount();
      }
    };
  }, []);

  const handleChange = (e: TInputChangeEvent) => {
    e.stopPropagation();
    handleChangeFiles(e);
  };

  const handleReset = (e: Event) => {
    e.preventDefault();
    setFilesMap(new Map());
  };

  useListeners({
    ref: hiddenInputRef,
    listeners: {
      "reset": handleReset,
    },
  });

  useImperativeHandle(ref, (): IFilesUploaderRef => ({
    el: containerRef.current,
    ref: null,
    getData: () => null,
    utils: null,
    stateActions: {
      setFiles: setFilesMap,
    },
  }), [ filesMap, setFilesMap ]);

  const computedStates = {
    isError, isDisabled, isDragging, isMultiple, isLoading,
  };

  const context = useMemo(() => ({
    filesMap,
    maxSize,
    maxFiles,
    getCN,
    fileTypes,
    isDisabled,
    isLoading,
    isError,
    isDragging,
    isMultiple,
    onDeleteClick: handleFileItemDeleteClick,
    onFileRender,
    hiddenInputRef,
    setFilesMap,
    slotFileItem,
    maxSizeType,
  }), [ filesMap, maxFiles, maxSize, fileTypes, isDisabled, isError, isDragging, isMultiple, onFileRender, handleFileItemDeleteClick, setFilesMap, slotFileItem, maxSizeType ]);

  const SSGComponent = useSSGRender("data-js-files-uploader", props);

  if (SSGComponent) {
    return SSGComponent;
  }

  return (
    <FilesUploaderCtx value={context}>
      <div
        ref={containerRef}
        className={getCN("", { ...computedStates, ...extraClasses })}
        {...extraAttrs}
        onDragStart={handleDragStart}
        onDragEnter={handleDragEnter}
        onDragLeave={handleDragLeave}
        onDragOver={handleDragOver}
        onDrop={handleDrop}
      >
        <Input defaultValue={""} isRequired={isRequired ? "file" : undefined} form={form ?? ""} type={"file"} baseClass={getCN("input")} data-js-component-input={"filesUploader"} id={id || name} name={name} ref={hiddenInputRef} extraAttrs={{
          "data-js-input-validation-bubble": "true",
          "data-js-component-input": "filesUploader",
        }} />
        <label className={getCN("container")}>
          <input
            type={"file"}
            accept={getConvertedFileTypes(fileTypes)}
            multiple={maxFiles > 1}
            className={getCN("input")}
            disabled={isDisabled || isLoading}
            onChange={handleChange}
            ref={inputRef}
          />
          <div className={getCN("zone")}>
            {(!!slotText && isValidElement(slotText)) ? cloneElement(slotText, {
              ...(slotText?.props ?? {}),
              ...computedStates,
            }) : <FilesUploaderText />}
          </div>
        </label>
        {(!!slotTip && isValidElement(slotTip)) ? cloneElement(slotTip, {
          ...(slotTip?.props ?? {}),
          ...computedStates,
        }) : <FilesUploaderTip />}
        {!!filesMap.size && ((!!slotFilesList && isValidElement(slotFilesList)) ? cloneElement(slotFilesList, {
          ...(slotFilesList?.props ?? {}),
          filesMap,
          handleFileItemDeleteClick,
          ...computedStates,
        } as HTMLAttributes<HTMLElement>) : <FilesUploaderList />)}
        {!!children && Children.map(children, ((child, index) => {
          if (isValidElement(child)) {
            return cloneElement(child, {
              ...(child?.props ?? {}),
              filesMap,
              key: `${index}`,
              ...computedStates,
            } as HTMLAttributes<HTMLElement>);
          }
          return child;
        }))}
      </div>
    </FilesUploaderCtx>
  );
};

export type { IFilesUploader, IFilesUploaderRef, IOnFilesChangeArg };

export { FilesUploader };
