<script setup lang="ts">
import { UiButton } from '@/components/UiButton';
import { UiIcon } from '@/components/UiIcon';
import { cn } from '@/lib/utils';
import { computed, ref } from 'vue';
import { formatFileSize, validateFiles } from './file-validation';
import type { UiFileUploadBoxEmits, UiFileUploadBoxProps } from './types';

defineOptions({
  name: 'UiFileUploadBox',
});

const props = withDefaults(defineProps<UiFileUploadBoxProps>(), {
  accept: undefined,
  multiple: true,
  maxFileSizeBytes: undefined,
  disabled: false,
  supportedTypesLabel: '',
  maxSizeLabel: '',
  dropLabel: 'Drop files here',
  chooseLabel: 'Choose files',
  ariaLabel: 'File upload',
});

const emit = defineEmits<UiFileUploadBoxEmits>();

const inputRef = ref<HTMLInputElement | null>(null);
const isDragging = ref(false);
let dragDepth = 0;

const maxSizeLine = computed(() => {
  const custom = props.maxSizeLabel?.trim();
  if (custom) return custom;
  if (props.maxFileSizeBytes != null) {
    return `Max size: ${formatFileSize(props.maxFileSizeBytes)}`;
  }
  return '';
});

const showFooter = computed(
  () => !!(props.supportedTypesLabel?.trim() || maxSizeLine.value),
);

function openPicker() {
  if (props.disabled) return;
  emit('pickerOpen');
  inputRef.value?.click();
}

function onZoneClick(e: MouseEvent) {
  if (props.disabled) return;
  emit('click', e);
  const t = e.target as HTMLElement | null;
  if (t?.closest?.('button')) return;
  openPicker();
}

function onZoneKeydown(e: KeyboardEvent) {
  if (props.disabled) return;
  emit('keydown', e);
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    openPicker();
  }
}

function onDragEnter(e: DragEvent) {
  e.preventDefault();
  e.stopPropagation();
  emit('dragenter', e);
  if (props.disabled) return;
  dragDepth += 1;
  isDragging.value = true;
}

function onDragLeave(e: DragEvent) {
  e.preventDefault();
  e.stopPropagation();
  emit('dragleave', e);
  if (props.disabled) return;
  dragDepth -= 1;
  if (dragDepth <= 0) {
    dragDepth = 0;
    isDragging.value = false;
  }
}

function onDragOver(e: DragEvent) {
  e.preventDefault();
  e.stopPropagation();
  emit('dragover', e);
  if (e.dataTransfer) {
    e.dataTransfer.dropEffect = props.disabled ? 'none' : 'copy';
  }
}

function onDrop(e: DragEvent) {
  e.preventDefault();
  e.stopPropagation();
  emit('drop', e);
  dragDepth = 0;
  isDragging.value = false;
  if (props.disabled) return;
  const dt = e.dataTransfer;
  if (!dt?.files?.length) return;
  processFileList(dt.files);
}

function onInputChange(e: Event) {
  emit('change', e);
  const input = e.target as HTMLInputElement;
  if (!input.files?.length) return;
  processFileList(input.files);
  input.value = '';
}

function processFileList(fileList: FileList) {
  const files = Array.from(fileList);
  const { valid, errors } = validateFiles(files, {
    accept: props.accept,
    maxBytes: props.maxFileSizeBytes,
  });
  for (const { file, message } of errors) {
    emit('error', { message, file });
  }
  // `multiple` on the hidden input only limits the OS picker; drag-and-drop can still
  // deliver many files — honor single-file mode here too (first valid file wins).
  if (!props.multiple) {
    if (valid[0]) emit('update:files', [valid[0]]);
    return;
  }
  if (valid.length) emit('update:files', valid);
}
</script>

<template>
  <div class="flex flex-col gap-3">
    <input
      ref="inputRef"
      type="file"
      class="sr-only"
      :accept="accept"
      :multiple="multiple"
      :disabled="disabled"
      :aria-hidden="true"
      tabindex="-1"
      @change="onInputChange"
    />

    <div
      :class="
        cn(
          'border-border-surface-default bg-background-surface-modal focus-visible:ring-ring relative flex cursor-pointer flex-col items-center justify-center gap-4 rounded-lg border border-dashed p-8 text-center transition-colors outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
          isDragging && 'border-1 border-border-surface-focus-ring',
          disabled && 'cursor-not-allowed opacity-50',
        )
      "
    >
      <!-- Drop zone: tabindex -1 when disabled removes it from tab order (aria-disabled). -->
      <!-- eslint-disable-next-line vuejs-accessibility/interactive-supports-focus -- intentional: disabled state is not keyboard-focusable -->
      <div
        role="button"
        :tabindex="disabled ? -1 : 0"
        :class="
          cn(
            'text-content-on-surface-primary flex flex-col items-center justify-center gap-4 outline-none',
            isDragging ? 'body-16-bold' : 'body-16-medium',
          )
        "
        :aria-disabled="disabled ? true : undefined"
        :aria-label="ariaLabel"
        @click="onZoneClick"
        @keydown="onZoneKeydown"
        @focus="emit('focus', $event)"
        @blur="emit('blur', $event)"
        @dragenter="onDragEnter"
        @dragleave="onDragLeave"
        @dragover="onDragOver"
        @drop="onDrop"
      >
        <div>
          <slot name="title">{{ dropLabel }}</slot>
        </div>

        <!-- Fixed 80×80 slot so inner circle can go 72 → 80 on drag without shifting the drop zone. -->
        <div
          class="flex size-20 shrink-0 items-center justify-center"
          aria-hidden="true"
        >
          <div
            :class="
              cn(
                'flex items-center justify-center rounded-full transition-all duration-50 ease-out',
                isDragging
                  ? 'size-20 bg-background-accent-soft'
                  : 'size-18 bg-background-surface-muted',
              )
            "
          >
            <slot name="icon">
              <UiIcon
                name="cloud-upload"
                :size="32"
                :class="
                  cn(
                    isDragging
                      ? 'text-content-on-surface-link'
                      : 'text-content-on-surface-muted',
                  )
                "
                aria-hidden="true"
              />
            </slot>
          </div>
        </div>
      </div>

      <slot name="choose">
        <UiButton
          type="button"
          variant="secondary"
          size="sm"
          :disabled="disabled"
          @click.stop="openPicker"
        >
          {{ chooseLabel }}
        </UiButton>
      </slot>
    </div>

    <slot name="footer">
      <div
        v-if="showFooter"
        class="text-muted-foreground flex flex-wrap items-center justify-between gap-2 label-12-regular"
      >
        <span v-if="supportedTypesLabel?.trim()">{{
          supportedTypesLabel
        }}</span>
        <span v-if="maxSizeLine" class="ml-auto">{{ maxSizeLine }}</span>
      </div>
    </slot>
  </div>
</template>
