<script setup lang="ts">
/**
 * Props-driven multi-select composition. Wraps the shadcn Combobox
 * primitive with badge chips showing the current selection.
 *
 *   <SelectMultipleOption v-model="tags" :options="[...]" label="Tags" />
 */
import { computed } from "vue"
import { X } from "@lucide/vue"
import type { SelectOptionLike } from "@/definitions/shared/select-options"
import {
  Combobox,
  ComboboxAnchor,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxItemIndicator,
} from "@/components/ui/combobox"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { FieldFrame } from "@/components/ui/field-frame"

export interface SelectMultipleOptionProps {
  modelValue?: Array<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<SelectMultipleOptionProps>(), {
  errors: () => [],
  options: () => [],
})

const model = defineModel<Array<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 selected = computed<string[]>(() => {
  const v = model.value
  if (!v) {
    return []
  }
  if (Array.isArray(v)) {
    return v.map((x) => String(x))
  }
  return [String(v)]
})

const labelFor = (val: string): string =>
  normalised.value.find((o) => o.value === val)?.label ?? val

function onUpdate(next: unknown): void {
  if (Array.isArray(next)) {
    model.value = next.map((v) => String(v))
  } else if (next === undefined || next === null) {
    model.value = []
  } else {
    model.value = [String(next)]
  }
}

function removeValue(val: string): void {
  model.value = selected.value.filter((v) => v !== val)
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :for="id"
    :help-text="helpText"
    :errors="errors"
  >
    <div
      v-if="selected.length"
      class="flex flex-wrap gap-1"
      :data-testid="`${id ?? 'select-multiple'}-chips`"
    >
      <Badge
        v-for="v in selected"
        :key="v"
        variant="secondary"
        class="flex items-center gap-1"
      >
        <span>{{ labelFor(v) }}</span>
        <Button
          v-if="!disabled && !readonly"
          type="button"
          variant="ghost"
          size="icon-sm"
          class="h-4 w-4 p-0"
          :aria-label="`Remove ${labelFor(v)}`"
          @click="removeValue(v)"
        >
          <X class="h-3 w-3" />
        </Button>
      </Badge>
    </div>
    <Combobox
      :model-value="selected"
      multiple
      :disabled="disabled || readonly"
      @update:model-value="onUpdate"
    >
      <ComboboxAnchor
        :data-element-name="name ?? undefined"
        :class="errors.length ? 'border-destructive ring-destructive' : ''"
      >
        <ComboboxInput
          :id="id"
          :placeholder="placeholder ?? 'Select multiple…'"
        />
      </ComboboxAnchor>
      <ComboboxContent>
        <ComboboxEmpty>No options match.</ComboboxEmpty>
        <ComboboxItem
          v-for="opt in normalised"
          :key="opt.value"
          :value="opt.value"
          :disabled="opt.disabled"
        >
          <ComboboxItemIndicator />
          {{ opt.label }}
        </ComboboxItem>
      </ComboboxContent>
    </Combobox>
  </FieldFrame>
</template>
