<script setup lang="ts">
/**
 * Props-driven file-upload composition.
 *
 * Visible Button triggers a hidden `<input type="file">`. Selected files
 * render as info chips with name, formatted size and a Remove control.
 * Emits a `File` (single) or `File[]` (multiple).
 *
 *   <UploadFile v-model="avatar" label="Avatar" accept="image/*" />
 */
import { computed, ref, watch } from "vue"
import { Upload, X } from "@lucide/vue"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { FieldFrame } from "@/components/ui/field-frame"

export interface UploadFileProps {
  modelValue?: File | File[] | null
  id?: string
  name?: string
  label?: string
  hideLabel?: boolean
  placeholder?: string
  accept?: string
  acceptedFileTypes?: string[]
  multiple?: boolean
  maxFileSize?: number
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  helpText?: string
  errors?: string[]
}

const props = withDefaults(defineProps<UploadFileProps>(), {
  errors: () => [],
  acceptedFileTypes: () => [],
})

const model = defineModel<File | File[] | null>("modelValue")

const fileInput = ref<HTMLInputElement | null>(null)
const files = ref<File[]>([])

watch(
  () => model.value,
  (next) => {
    if (next instanceof File) {
      files.value = [next]
    } else if (Array.isArray(next) && next.every((f) => f instanceof File)) {
      files.value = next as File[]
    } else {
      files.value = []
    }
  },
  { immediate: true },
)

const accept = computed<string>(() => {
  if (props.accept) {
    return props.accept
  }
  const list = props.acceptedFileTypes
  return Array.isArray(list) && list.length ? list.join(",") : ""
})

const maxBytes = computed<number | undefined>(() => props.maxFileSize)

function openPicker(): void {
  fileInput.value?.click()
}

function emitFiles(): void {
  if (props.multiple) {
    model.value = files.value.slice()
  } else {
    model.value = files.value[0] ?? null
  }
}

function onChange(event: Event): void {
  const target = event.target as HTMLInputElement
  const incoming = target.files ? Array.from(target.files) : []
  if (!incoming.length) {
    return
  }
  const tooLarge = maxBytes.value
    ? incoming.find((f) => f.size > (maxBytes.value as number))
    : undefined
  if (tooLarge) {
    emitFiles()
    return
  }
  if (props.multiple) {
    files.value = [...files.value, ...incoming]
  } else {
    files.value = [incoming[0]!]
  }
  emitFiles()
  target.value = ""
}

function removeFile(index: number): void {
  files.value.splice(index, 1)
  emitFiles()
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) {
    return `${bytes} B`
  }
  if (bytes < 1024 * 1024) {
    return `${(bytes / 1024).toFixed(1)} KB`
  }
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :help-text="helpText"
    :errors="errors"
  >
    <div :data-element-name="name ?? undefined">
      <Button
        type="button"
        variant="outline"
        :disabled="disabled || readonly"
        data-testid="upload-trigger"
        @click="openPicker"
      >
        <Upload class="mr-2 size-4" />
        <span>{{ placeholder ?? (multiple ? "Add files" : "Choose file") }}</span>
      </Button>
      <input
        ref="fileInput"
        type="file"
        class="sr-only"
        :accept="accept || undefined"
        :multiple="multiple"
        :disabled="disabled || readonly"
        :required="required"
        :name="name ?? undefined"
        data-testid="upload-input"
        @change="onChange"
      >
    </div>

    <div
      v-if="files.length"
      class="flex flex-col gap-2"
    >
      <Card
        v-for="(file, i) in files"
        :key="`${file.name}-${i}`"
        class="border"
      >
        <CardContent class="flex items-center justify-between gap-3 py-3">
          <div class="flex flex-col">
            <span class="text-sm font-medium">{{ file.name }}</span>
            <span class="text-xs text-muted-foreground">{{ formatBytes(file.size) }}</span>
          </div>
          <Button
            type="button"
            variant="ghost"
            size="icon-sm"
            aria-label="Remove file"
            :disabled="disabled || readonly"
            data-testid="upload-remove"
            @click="removeFile(i)"
          >
            <X class="size-4" />
          </Button>
        </CardContent>
      </Card>
    </div>
  </FieldFrame>
</template>
