<!--
	Installed from @ieedan/shadcn-svelte-extras
-->

<script lang="ts">
  import { cn } from "$lib/utils";
  import UploadIcon from "@lucide/svelte/icons/upload";
  import { displaySize } from ".";
  import { useId } from "bits-ui";
  import type { FileDropZoneProps, FileRejectedReason } from "./types";

  let {
    id = useId(),
    children,
    maxFiles,
    maxFileSize,
    fileCount,
    disabled = false,
    onUpload,
    onFileRejected,
    accept,
    class: className,
    ...rest
  }: FileDropZoneProps = $props();

  if (maxFiles !== undefined && fileCount === undefined) {
    console.warn(
      "Make sure to provide FileDropZone with `fileCount` when using the `maxFiles` prompt",
    );
  }

  let uploading = $state(false);

  const drop = async (
    e: DragEvent & {
      currentTarget: EventTarget & HTMLLabelElement;
    },
  ) => {
    if (disabled || !canUploadFiles) return;

    e.preventDefault();

    const droppedFiles = Array.from(e.dataTransfer?.files ?? []);

    await upload(droppedFiles);
  };

  const change = async (
    e: Event & {
      currentTarget: EventTarget & HTMLInputElement;
    },
  ) => {
    if (disabled) return;

    const selectedFiles = e.currentTarget.files;

    if (!selectedFiles) return;

    await upload(Array.from(selectedFiles));

    // this if a file fails and we upload the same file again we still get feedback
    (e.target as HTMLInputElement).value = "";
  };

  const shouldAcceptFile = (file: File, fileNumber: number): FileRejectedReason | undefined => {
    if (maxFileSize !== undefined && file.size > maxFileSize) return "Maximum file size exceeded";

    if (maxFiles !== undefined && fileNumber > maxFiles) return "Maximum files uploaded";

    if (!accept) return undefined;

    const acceptedTypes = accept.split(",").map((a) => a.trim().toLowerCase());
    const fileType = file.type.toLowerCase();
    const fileName = file.name.toLowerCase();

    const isAcceptable = acceptedTypes.some((pattern) => {
      // check extension like .mp4
      if (fileType.startsWith(".")) {
        return fileName.endsWith(pattern);
      }

      // if pattern has wild card like video/*
      if (pattern.endsWith("/*")) {
        const baseType = pattern.slice(0, pattern.indexOf("/*"));
        return fileType.startsWith(baseType + "/");
      }

      // otherwise it must be a specific type like video/mp4
      return fileType === pattern;
    });

    if (!isAcceptable) return "File type not allowed";

    return undefined;
  };

  const upload = async (uploadFiles: File[]) => {
    uploading = true;

    const validFiles: File[] = [];

    for (let i = 0; i < uploadFiles.length; i++) {
      const file = uploadFiles[i];

      const rejectedReason = shouldAcceptFile(file, (fileCount ?? 0) + i + 1);

      if (rejectedReason) {
        onFileRejected?.({ file, reason: rejectedReason });
        continue;
      }

      validFiles.push(file);
    }

    await onUpload(validFiles);

    uploading = false;
  };

  const canUploadFiles = $derived(
    !disabled &&
      !uploading &&
      !(maxFiles !== undefined && fileCount !== undefined && fileCount >= maxFiles),
  );
</script>

<label
  ondragover={(e) => e.preventDefault()}
  ondrop={drop}
  for={id}
  aria-disabled={!canUploadFiles}
  class={cn(
    "border-border hover:bg-accent/25 flex h-48 w-full place-items-center justify-center rounded-lg border-2 border-dashed p-6 transition-all hover:cursor-pointer aria-disabled:opacity-50 aria-disabled:hover:cursor-not-allowed",
    className,
  )}
>
  {#if children}
    {@render children()}
  {:else}
    <div class="flex flex-col place-items-center justify-center gap-2">
      <div
        class="border-border text-muted-foreground flex size-14 place-items-center justify-center rounded-full border border-dashed"
      >
        <UploadIcon class="size-7" />
      </div>
      <div class="flex flex-col gap-0.5 text-center">
        <span class="text-muted-foreground font-medium">
          Drag 'n' drop files here, or click to select files
        </span>
        {#if maxFiles || maxFileSize}
          <span class="text-muted-foreground/75 text-sm">
            {#if maxFiles}
              <span>You can upload {maxFiles} files</span>
            {/if}
            {#if maxFiles && maxFileSize}
              <span>(up to {displaySize(maxFileSize)} each)</span>
            {/if}
            {#if maxFileSize && !maxFiles}
              <span>Maximum size {displaySize(maxFileSize)}</span>
            {/if}
          </span>
        {/if}
      </div>
    </div>
  {/if}
  <input
    {...rest}
    disabled={!canUploadFiles}
    {id}
    {accept}
    multiple={maxFiles === undefined || maxFiles - (fileCount ?? 0) > 1}
    type="file"
    onchange={change}
    class="hidden"
  />
</label>
