<script setup lang="ts">
/**
 * Props-driven asset-picker composition. The consumer supplies
 * Promise-returning `onBrowse` (and optional `onUpload`) callbacks; the
 * composition itself never makes HTTP calls.
 *
 *   <AssetPickerFile
 *     v-model="asset"
 *     label="Hero image"
 *     :on-browse="() => api.assets.list()"
 *     :on-upload="(file) => api.assets.upload(file)"
 *   />
 */
import { computed, ref } from "vue"
import { ImageIcon, Loader2, Upload, X } from "@lucide/vue"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { FieldFrame } from "@/components/ui/field-frame"
import { Input } from "@/components/ui/input"
import { Skeleton } from "@/components/ui/skeleton"

export interface Asset {
  id: string
  url: string
  name?: string
  thumbnailUrl?: string
  mimeType?: string
  sizeBytes?: number
}

export type AssetPickerValue = Asset | { id?: string; url?: string; name?: string } | string | null

export interface AssetPickerFileProps {
  modelValue?: AssetPickerValue
  id?: string
  name?: string
  label?: string
  hideLabel?: boolean
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  helpText?: string
  errors?: string[]
  /** Required — called when the picker dialog opens. */
  onBrowse?: () => Promise<Asset[]>
  /** Optional — when provided, an "Upload new" button appears in the dialog. */
  onUpload?: (file: File) => Promise<Asset>
  /** File-input `accept` attribute for the upload flow. */
  acceptedFileTypes?: string[]
}

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

const model = defineModel<AssetPickerValue>("modelValue")

const open = ref<boolean>(false)
const isLoading = ref<boolean>(false)
const isUploading = ref<boolean>(false)
const assets = ref<Asset[]>([])
const filter = ref<string>("")
const fileInput = ref<HTMLInputElement | null>(null)

let browseToken = 0

const selectedAsset = computed<Asset | null>(() => {
  const raw = model.value
  if (!raw) {
    return null
  }
  if (typeof raw === "string") {
    return { id: raw, url: raw, name: raw }
  }
  if (typeof raw === "object") {
    const obj = raw as { id?: string; url?: string; name?: string; thumbnailUrl?: string }
    return {
      id: obj.id ?? "",
      url: obj.url ?? "",
      name: obj.name,
      thumbnailUrl: obj.thumbnailUrl,
    }
  }
  return null
})

const acceptAttr = computed<string | undefined>(() => {
  if (!props.acceptedFileTypes?.length) {
    return undefined
  }
  return props.acceptedFileTypes.join(",")
})

const filteredAssets = computed<Asset[]>(() => {
  const q = filter.value.trim().toLowerCase()
  if (!q) {
    return assets.value
  }
  return assets.value.filter((a) =>
    (a.name ?? a.id).toLowerCase().includes(q),
  )
})

async function loadAssets(): Promise<void> {
  if (!props.onBrowse) {
    assets.value = []
    return
  }
  const myToken = ++browseToken
  isLoading.value = true
  try {
    const next = await props.onBrowse()
    if (myToken !== browseToken) {
      return
    }
    assets.value = Array.isArray(next) ? next : []
  } catch {
    if (myToken !== browseToken) {
      return
    }
    assets.value = []
  } finally {
    if (myToken === browseToken) {
      isLoading.value = false
    }
  }
}

function onOpenChange(next: boolean): void {
  open.value = next
  if (next) {
    filter.value = ""
    void loadAssets()
  }
}

function selectAsset(asset: Asset): void {
  model.value = asset
  open.value = false
}

function clearAsset(): void {
  model.value = null
}

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

async function onUploadChange(event: Event): Promise<void> {
  const target = event.target as HTMLInputElement
  const file = target.files?.[0]
  target.value = ""
  if (!file || !props.onUpload) {
    return
  }
  isUploading.value = true
  try {
    const asset = await props.onUpload(file)
    if (asset) {
      model.value = asset
      open.value = false
    }
  } catch {
    // surface via consumer's error handling — composition stays silent
  } finally {
    isUploading.value = false
  }
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :for="id"
    :help-text="helpText"
    :errors="errors"
  >
    <div :data-element-name="name ?? undefined" data-testid="asset-picker">
      <Card v-if="selectedAsset" data-testid="asset-picker-chip">
        <CardContent class="flex items-center gap-3 py-3">
          <img
            v-if="selectedAsset.thumbnailUrl || selectedAsset.url"
            :src="selectedAsset.thumbnailUrl ?? selectedAsset.url"
            :alt="selectedAsset.name ?? 'Selected asset'"
            class="size-10 rounded object-cover"
            data-testid="asset-picker-chip-thumb"
          >
          <ImageIcon v-else class="size-5 text-muted-foreground" />
          <span class="flex-1 truncate text-sm">
            {{ selectedAsset.name ?? selectedAsset.id ?? "Selected asset" }}
          </span>
          <Button
            type="button"
            variant="ghost"
            size="icon-sm"
            aria-label="Remove asset"
            :disabled="disabled || readonly"
            data-testid="asset-picker-remove"
            @click="clearAsset"
          >
            <X class="size-4" />
          </Button>
        </CardContent>
      </Card>

      <Dialog :open="open" @update:open="onOpenChange">
        <DialogTrigger as-child>
          <Button
            type="button"
            variant="outline"
            :disabled="disabled || readonly"
            :class="selectedAsset ? 'mt-2' : ''"
            data-testid="asset-picker-browse"
          >
            <ImageIcon class="mr-2 size-4" />
            {{ selectedAsset ? "Change…" : "Browse…" }}
          </Button>
        </DialogTrigger>
        <DialogContent class="sm:max-w-2xl">
          <DialogHeader>
            <DialogTitle>Select an asset</DialogTitle>
          </DialogHeader>

          <div class="flex items-center gap-2">
            <Input
              v-model="filter"
              placeholder="Search assets…"
              data-testid="asset-picker-search"
            />
            <Button
              v-if="onUpload"
              type="button"
              variant="outline"
              :disabled="isUploading"
              data-testid="asset-picker-upload"
              @click="openUploadPicker"
            >
              <Loader2
                v-if="isUploading"
                class="mr-2 size-4 animate-spin"
              />
              <Upload v-else class="mr-2 size-4" />
              Upload new
            </Button>
            <input
              v-if="onUpload"
              ref="fileInput"
              type="file"
              class="sr-only"
              :accept="acceptAttr"
              data-testid="asset-picker-upload-input"
              @change="onUploadChange"
            >
          </div>

          <div
            v-if="isLoading"
            class="grid grid-cols-3 gap-3"
            data-testid="asset-picker-skeleton"
          >
            <Skeleton v-for="i in 6" :key="i" class="aspect-square w-full" />
          </div>
          <div
            v-else-if="filteredAssets.length === 0"
            class="py-8 text-center text-sm text-muted-foreground"
            data-testid="asset-picker-empty"
          >
            No assets available.
          </div>
          <div
            v-else
            class="grid max-h-96 grid-cols-3 gap-3 overflow-y-auto"
            data-testid="asset-picker-grid"
          >
            <button
              v-for="asset in filteredAssets"
              :key="asset.id"
              type="button"
              class="flex flex-col items-stretch overflow-hidden rounded-md border bg-card text-left hover:border-primary focus:outline-none focus:ring-2 focus:ring-ring"
              :data-testid="`asset-picker-item-${asset.id}`"
              @click="selectAsset(asset)"
            >
              <div class="flex aspect-square items-center justify-center bg-muted">
                <img
                  v-if="asset.thumbnailUrl || asset.url"
                  :src="asset.thumbnailUrl ?? asset.url"
                  :alt="asset.name ?? asset.id"
                  class="size-full object-cover"
                >
                <ImageIcon v-else class="size-8 text-muted-foreground" />
              </div>
              <div class="truncate p-2 text-xs">
                {{ asset.name ?? asset.id }}
              </div>
            </button>
          </div>
        </DialogContent>
      </Dialog>
    </div>
  </FieldFrame>
</template>
