<script setup lang="ts">
/**
 * Props-driven icon picker composition. Searchable combobox over a
 * curated subset of lucide icon names. The selected value is the icon's
 * PascalCase name (e.g. `"Calendar"`); the currently-selected icon is
 * rendered as a live preview alongside the combobox input.
 *
 *   <IconPicker v-model="icon" label="Icon" />
 */
import { computed, h, ref } from "vue"
import * as LucideIcons from "@lucide/vue"
import type { Component } from "vue"
import {
  Combobox,
  ComboboxAnchor,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxItemIndicator,
} from "@/components/ui/combobox"
import { FieldFrame } from "@/components/ui/field-frame"

export interface IconPickerProps {
  modelValue?: string | null
  id?: string
  name?: string
  label?: string
  hideLabel?: boolean
  placeholder?: string
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  helpText?: string
  errors?: string[]
}

withDefaults(defineProps<IconPickerProps>(), {
  errors: () => [],
})

const model = defineModel<string | null>("modelValue")

const query = ref<string>("")

/**
 * A small curated subset of lucide icon names. We don't enumerate the
 * whole 1.4k+ set because we render the preview component eagerly and
 * Storybook bundles every story.
 */
const ICON_NAMES = [
  "Activity",
  "AlertCircle",
  "Archive",
  "Bell",
  "Bookmark",
  "Briefcase",
  "Calendar",
  "Camera",
  "Check",
  "ChevronRight",
  "Clock",
  "Cloud",
  "Code",
  "Copy",
  "Database",
  "Download",
  "Edit",
  "Eye",
  "Facebook",
  "File",
  "FileText",
  "Filter",
  "Flag",
  "Folder",
  "Gift",
  "Globe",
  "Heart",
  "Home",
  "Image",
  "Inbox",
  "Info",
  "Layers",
  "Link",
  "Lock",
  "LogIn",
  "LogOut",
  "Mail",
  "MapPin",
  "Menu",
  "MessageSquare",
  "Mic",
  "Monitor",
  "Moon",
  "Music",
  "Paperclip",
  "Pause",
  "Phone",
  "PieChart",
  "Play",
  "Plus",
  "Power",
  "Printer",
  "RefreshCw",
  "Save",
  "Search",
  "Send",
  "Settings",
  "Share",
  "ShieldCheck",
  "ShoppingCart",
  "Star",
  "Sun",
  "Tag",
  "Trash",
  "Twitter",
  "Upload",
  "User",
  "Video",
  "Wifi",
  "Wrench",
  "X",
  "Zap",
] as const

const filtered = computed<readonly string[]>(() => {
  const q = query.value.trim().toLowerCase()
  if (!q) {
    return ICON_NAMES
  }
  return ICON_NAMES.filter((name) => name.toLowerCase().includes(q))
})

function resolveIcon(name: string): Component | undefined {
  if (!name) {
    return undefined
  }
  const registry = LucideIcons as unknown as Record<
    string,
    Component | undefined
  >
  return registry[name]
}

const previewIcon = computed<Component | undefined>(() =>
  resolveIcon(model.value ?? ""),
)

function renderIcon(name: string) {
  const icon = resolveIcon(name)
  return icon ? h(icon, { class: "size-4" }) : null
}

function onSelect(value: unknown): void {
  model.value = typeof value === "string" ? value : ""
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :for="id"
    :help-text="helpText"
    :errors="errors"
  >
    <Combobox
      :model-value="model ?? ''"
      :disabled="disabled"
      @update:model-value="onSelect"
    >
      <ComboboxAnchor
        :data-element-name="name ?? undefined"
        :data-readonly="readonly || undefined"
      >
        <component
          :is="previewIcon"
          v-if="previewIcon"
          class="mr-2 size-4 shrink-0"
          data-testid="icon-preview"
        />
        <ComboboxInput
          v-model="query"
          :placeholder="placeholder ?? 'Search icons…'"
          :readonly="readonly"
          :required="required"
        />
      </ComboboxAnchor>
      <ComboboxContent>
        <ComboboxEmpty>No icons match.</ComboboxEmpty>
        <ComboboxItem v-for="iconName in filtered" :key="iconName" :value="iconName">
          <ComboboxItemIndicator />
          <component :is="renderIcon(iconName)" />
          <span class="ml-2">{{ iconName }}</span>
        </ComboboxItem>
      </ComboboxContent>
    </Combobox>
  </FieldFrame>
</template>
