<script setup lang="ts">
/**
 * Props-driven single-select composition. Composes the shadcn `Select`
 * primitive with `FieldFrame` chrome.
 *
 * Self-contained — usable directly in any Vue app:
 *
 *   <SelectOption v-model="role" :options="[...]" label="Role" />
 *
 * The JSON-driven engine renders this via `ElementRenderer`, which binds
 * `modelValue` / `errors` to the form-data store automatically.
 */
import { computed } from "vue"
import type { SelectOptionLike } from "@/definitions/shared/select-options"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { FieldFrame } from "@/components/ui/field-frame"

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

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

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

interface NormalisedOption {
  label: string
  value: string
  disabled: boolean
}

function normaliseOption(opt: SelectOptionLike): NormalisedOption {
  if (typeof opt === "string") {
    return { label: opt, value: opt, disabled: false }
  }
  return {
    label: opt.label,
    value: String(opt.value),
    disabled: Boolean(opt.disabled),
  }
}

const normalised = computed<NormalisedOption[]>(() =>
  (props.options ?? []).map(normaliseOption),
)

const triggerValue = computed<string | undefined>(() => {
  if (model.value === undefined || model.value === null) {
    return undefined
  }
  return String(model.value)
})

function onUpdate(next: unknown): void {
  if (next === undefined || next === null) {
    model.value = null
    return
  }
  if (typeof next === "string" || typeof next === "number") {
    model.value = next
    return
  }
  if (Array.isArray(next)) {
    const first = next[0]
    model.value =
      typeof first === "string" || typeof first === "number" ? first : null
    return
  }
  model.value = String(next)
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :for="id"
    :help-text="helpText"
    :errors="errors"
  >
    <Select
      :model-value="triggerValue"
      :disabled="disabled || readonly"
      :name="name"
      :required="required"
      @update:model-value="onUpdate"
    >
      <SelectTrigger
        :id="id"
        :data-element-name="name ?? undefined"
        :class="errors.length ? 'border-destructive ring-destructive' : ''"
      >
        <SelectValue :placeholder="placeholder ?? 'Select…'" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem
          v-for="opt in normalised"
          :key="opt.value"
          :value="opt.value"
          :disabled="opt.disabled"
        >
          {{ opt.label }}
        </SelectItem>
      </SelectContent>
    </Select>
  </FieldFrame>
</template>
