"use client";

import * as React from "react";
import { FileText, Upload, X } from "lucide-react";
import Dropzone, {
  type DropzoneProps,
  type FileRejection,
} from "react-dropzone";
import { toast } from "sonner";

import { Button } from "@/shared/common/ui/button";
import { Progress } from "@/shared/common/ui/progress";
import { ScrollArea } from "@/shared/common/ui/scroll-area";
import { useControllableState } from "./hooks/use-controllable-state";
import { cn } from "@/shared/lib/utils";
import { formatBytes } from "./utils";
import { useTranslation } from "react-i18next";
import { t } from "i18next";
import { ClientSideValidator } from "@/shared/services/validation/ClientSideValidator";
import { FileUploadValidator } from "@/shared/services/validation/FileUploadValidator";

interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * Value of the uploader.
   * @type File[]
   * @default undefined
   * @example value={files}
   */
  value?: File[];

  /**
   * Function to be called when the value changes.
   * @type (files: File[]) => void
   * @default undefined
   * @example onValueChange={(files) => setFiles(files)}
   */
  onValueChange: (files: File[]) => void | null;

  /**
   * Function to be called when files are uploaded.
   * @type (files: File[]) => Promise<void>
   * @default undefined
   * @example onUpload={(files) => uploadFiles(files)}
   */
  onUpload?: (files: File[]) => Promise<void>;

  /**
   * Progress of the uploaded files.
   * @type Record<string, number> | undefined
   * @default undefined
   * @example progresses={{ "file1.png": 50 }}
   */
  progresses?: Record<string, number>;

  /**
   * Accepted file types for the uploader.
   * @type { [key: string]: string[]}
   * @default
   * ```ts
   * { "image/*": [] }
   * ```
   * @example accept={["image/png", "image/jpeg"]}
   */
  accept?: DropzoneProps["accept"];

  /**
   * Maximum file size for the uploader.
   * @type number | undefined
   * @default 1024 * 1024 * 2 // 2MB
   * @example maxSize={1024 * 1024 * 2} // 2MB
   */
  maxSize?: DropzoneProps["maxSize"];

  /**
   * Maximum number of files for the uploader.
   * @type number | undefined
   * @default 1
   * @example maxFileCount={4}
   */
  maxFileCount?: DropzoneProps["maxFiles"];

  /**
   * Whether the uploader should accept multiple files.
   * @type boolean
   * @default false
   * @example multiple
   */
  multiple?: boolean;
  canUploadIndividual?: boolean;
  uploadIndividualFun?: () => void;

  /**
   * Whether the uploader is disabled.
   * @type boolean
   * @default false
   * @example disabled
   */
  disabled?: boolean;
}

export function FileUploader(props: FileUploaderProps) {
  const {
    value: valueProp,
    onValueChange,
    onUpload,
    progresses,
    accept = {
      "/": [],
    },
    maxSize = 1024 * 1024 * 200,
    maxFileCount = 4,
    multiple = false,
    disabled = false,
    className,
    canUploadIndividual = false,
    uploadIndividualFun,
    ...dropzoneProps
  } = props;
  const { t } = useTranslation();
  const [files, setFiles] = useControllableState({
    prop: valueProp,
    onChange: onValueChange,
  });
  const validatorRef = React.useRef(new ClientSideValidator());

  const onDrop = React.useCallback(
    async (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
      if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
        toast.error(
          `${t("PDF.cannotUploadMoreThan")} ${maxFileCount} ${t("PDF.files")}`
        );
        return;
      }

      // Validate files using comprehensive validator
      const validatedFiles: File[] = [];
      const invalidFiles: string[] = [];

      for (const file of acceptedFiles) {
        const allowedMimeTypes = accept
          ? Object.keys(accept)
              .map((mime) => mime.trim().toLowerCase())
              .filter(
                (mime) =>
                  mime.includes("/") &&
                  mime !== "/" &&
                  mime !== "*" &&
                  mime !== "*/*"
              )
          : undefined;
        const allowedExtensions = accept
          ? Object.values(accept).flatMap((extensions) =>
              (extensions || []).map((ext) =>
                ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`
              )
            )
          : undefined;

        // Basic validation (size, extension, filename, MIME type)
        const basicValidation = FileUploadValidator.validateFile(file, {
          maxSizeMB: maxSize ? maxSize / (1024 * 1024) : undefined,
          allowedMimeTypes:
            file.type && allowedMimeTypes?.length ? allowedMimeTypes : undefined,
          allowedExtensions:
            allowedExtensions && allowedExtensions.length > 0
              ? allowedExtensions
              : undefined,
        });

        if (!basicValidation.isValid) {
          invalidFiles.push(`${file.name}: ${basicValidation.error}`);
          continue;
        }

        // Client-side magic byte validation
        const clientValidation = await validatorRef.current.validateUpload(
          file,
          file.type,
          allowedMimeTypes || []
        );

        if (!clientValidation.isValid) {
          invalidFiles.push(`${file.name}: ${clientValidation.error}`);
          continue;
        }

        validatedFiles.push(file);
      }

      // Show errors for invalid files
      if (invalidFiles.length > 0) {
        invalidFiles.forEach((error) => {
          toast.error(error);
        });
      }

      const newFiles = validatedFiles.map((file) =>
        Object.assign(file, {
          preview: URL.createObjectURL(file),
        })
      );

      const updatedFiles = files ? [...files, ...newFiles] : newFiles;

      setFiles(updatedFiles);

      if (rejectedFiles.length > 0) {
        rejectedFiles.forEach(({ file }) => {
          toast.error(`${t("PDF.file")} ${file.name} ${t("PDF.wasRejected")}`);
        });
      }

      if (
        onUpload &&
        updatedFiles.length > 0 &&
        updatedFiles.length <= maxFileCount
      ) {
        const target =
          updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;

        toast.promise(onUpload(updatedFiles), {
          loading: `${t("PDF.uploading")} ${target}...`,
          success: () => {
            setFiles([]);
            return `${target} ${t("PDF.uploaded")}`;
          },
          error: `${t("PDF.failedToUpload")} ${target}`,
        });
      }
    },

    [files, maxFileCount, multiple, onUpload, setFiles, t, maxSize, accept]
  );

  function onRemove(index: number) {
    if (!files) return;
    const newFiles = files.filter((_, i) => i !== index);
    setFiles(newFiles);
    onValueChange?.(newFiles);
  }

  // Revoke preview url when component unmounts
  React.useEffect(() => {
    return () => {
      if (!files) return;
      files.forEach((file) => {
        if (isFileWithPreview(file)) {
          URL.revokeObjectURL(file.preview);
        }
      });
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;

  return (
    <div className="relative flex flex-col gap-6 overflow-hidden">
      {onValueChange !== null && (
        <Dropzone
          onDrop={onDrop}
          accept={accept}
          maxSize={maxSize}
          maxFiles={maxFileCount}
          multiple={maxFileCount > 1 || multiple}
          disabled={isDisabled}
        >
          {({ getRootProps, getInputProps, isDragActive }) => (
            <div
              {...getRootProps()}
              className={cn(
                "group relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25",
                "ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
                "dark:bg-gray-900/50 dark:border-gray-600 dark:hover:bg-gray-800/60",
                isDragActive && "border-muted-foreground/50 dark:border-gray-500",
                isDisabled && "pointer-events-none opacity-60",
                className
              )}
              {...dropzoneProps}
            >
              <input {...getInputProps()} />
              {isDragActive ? (
                <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
                  <div className="rounded-full border border-dashed border-muted-foreground/50 p-3 dark:border-gray-500">
                    <Upload
                      className="size-7 text-muted-foreground"
                      aria-hidden="true"
                    />
                  </div>
                  <p className="font-medium text-muted-foreground">
                    {t("PDF.dropFilesHere")}
                  </p>
                </div>
              ) : (
                <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
                  <div className="rounded-full border border-dashed border-muted-foreground/50 p-3 dark:border-gray-500">
                    <Upload
                      className="size-7 text-muted-foreground"
                      aria-hidden="true"
                    />
                  </div>
                  <div className="flex flex-col gap-px">
                    <p className="font-medium text-muted-foreground">
                      {t("PDF.dragDropInstruction")}
                    </p>
                    <p className="text-sm text-muted-foreground/70">
                      {t("PDF.youCanUpload")}
                      {maxFileCount > 1
                        ? ` ${
                            maxFileCount === Infinity
                              ? "multiple"
                              : maxFileCount
                          }
                      ${t("PDF.filesUpTo")} ${formatBytes(maxSize)} )`
                        : ` ${t("PDF.aFileWith")} ${formatBytes(maxSize)}`}
                    </p>
                  </div>
                </div>
              )}
            </div>
          )}
        </Dropzone>
      )}
      {files?.length ? (
        <ScrollArea className="h-fit w-full px-3">
          <div className="flex max-h-48 flex-col gap-4">
            {files?.map((file, index) => (
              <FileCard
                key={index}
                file={file}
                onRemove={() => onRemove(index)}
                progress={progresses?.[file.name]}
                canUploadIndividual={canUploadIndividual}
                uploadIndividualFun={uploadIndividualFun ||  (() => {})}
              />
            ))}
          </div>
        </ScrollArea>
      ) : null}
    </div>
  );
}

interface FileCardProps {
  file: File;
  onRemove: () => void;
  progress?: number;
  canUploadIndividual?: boolean;
  uploadIndividualFun: () => void;
}

function FileCard({
  file,
  progress,
  onRemove,
  canUploadIndividual = false,
  uploadIndividualFun,
}: FileCardProps) {
  return (
    <div className="relative flex items-center gap-2.5 rounded-md border border-transparent p-2 dark:border-gray-700 dark:bg-gray-800/50">
      <div className="flex flex-1 gap-2.5">
        {isFileWithPreview(file) ? <FilePreview file={file} /> : null}
        <div className="flex w-full flex-col gap-2">
          <div className="flex flex-col gap-px">
            <p className="line-clamp-1 text-sm font-medium text-foreground/80">
              {file.name}
            </p>
            <p className="text-xs text-muted-foreground">
              {formatBytes(file.size)}
            </p>
          </div>
          {progress ? <Progress value={progress} /> : null}
        </div>
      </div>
      <div className="flex items-center gap-2">
        <Button
          type="button"
          variant="outline"
          size="icon"
          className="size-7 dark:border-gray-600 dark:hover:bg-gray-700"
          onClick={onRemove}
        >
          <X className="size-4" aria-hidden="true" />
          <span className="sr-only">{t("PDF.removeFile")}</span>
        </Button>
        {canUploadIndividual && (
          <Button type="button" variant="outline" className="dark:border-gray-600 dark:hover:bg-gray-700" onClick={uploadIndividualFun}>
            upload
          </Button>
        )}
      </div>
    </div>
  );
}

function isFileWithPreview(file: File): file is File & { preview: string } {
  return "preview" in file && typeof file.preview === "string";
}

interface FilePreviewProps {
  file: File & { preview: string };
}

function FilePreview({ file }: FilePreviewProps) {
  if (file.type.startsWith("image/")) {
    return (
      <img
        className="h-auto max-w-full"
        src={file.preview}
        alt={file.name}
        width={48}
        height={48}
        loading="lazy"
      />
    );
  }
  {
    /* <Image
        src={file.preview}
        alt={file.name}
        width={48}
        height={48}
        loading="lazy"
        className="aspect-square shrink-0 rounded-md object-cover"
      /> */
  }
  return (
    <FileText className="size-10 text-muted-foreground" aria-hidden="true" />
  );
}
