<script setup lang="ts">
import type { AcceptableValue } from 'reka-ui';
import { computed, ref } from 'vue';
import { Select as ShadcnSelect } from '@/components/core/select';
import type {
  UiSelectProps,
  UiSelectEmits,
  UiSelectOptionalValue,
} from './types';

defineOptions({
  name: 'UiSelect',
});

const props = withDefaults(defineProps<UiSelectProps>(), {
  modelValue: undefined,
  defaultValue: undefined,
  defaultOpen: false,
  open: undefined,
  disabled: false,
  required: false,
  name: undefined,
  dir: 'ltr',
  multiple: false,
});

const emit = defineEmits<UiSelectEmits>();

function toArray(value: UiSelectOptionalValue): string[] {
  if (Array.isArray(value)) return value;
  if (typeof value === 'string' && value.length > 0) return [value];
  return [];
}

function toSelectValue(value: UiSelectOptionalValue): UiSelectOptionalValue {
  if (props.multiple) {
    if (value === undefined) {
      return undefined;
    }

    return toArray(value);
  }

  if (typeof value === 'string') {
    return value;
  }

  return undefined;
}

const selectModelValue = computed(() => toSelectValue(props.modelValue));

const selectDefaultValue = computed(() => toSelectValue(props.defaultValue));

const isMultiControlled = computed(() => props.modelValue !== undefined);
const internalMultiValue = ref<string[]>(toArray(props.defaultValue));
const multiSelectedValues = computed<string[]>(() =>
  isMultiControlled.value
    ? toArray(props.modelValue)
    : internalMultiValue.value,
);
const formHiddenValues = computed<string[]>(() =>
  props.multiple && props.name ? multiSelectedValues.value : [],
);

function toStringArray(value: AcceptableValue | AcceptableValue[]): string[] {
  if (Array.isArray(value)) {
    return value.filter((v): v is string => typeof v === 'string');
  }
  if (typeof value === 'string') {
    return [value];
  }
  return [];
}

function handleModelValueUpdate(value: AcceptableValue | AcceptableValue[]) {
  if (props.multiple) {
    const next = toStringArray(value);
    if (!isMultiControlled.value) {
      internalMultiValue.value = next;
    }
    emit('update:modelValue', next);
    return;
  }

  if (typeof value === 'string') {
    emit('update:modelValue', value);
  }
}
</script>

<template>
  <ShadcnSelect
    :model-value="selectModelValue"
    :default-value="selectDefaultValue"
    :default-open="props.defaultOpen"
    :open="props.open"
    :disabled="props.disabled"
    :required="props.multiple ? false : props.required"
    :name="props.multiple ? undefined : props.name"
    :dir="props.dir"
    :multiple="props.multiple"
    @update:model-value="handleModelValueUpdate"
    @update:open="emit('update:open', $event)"
  >
    <slot />
  </ShadcnSelect>

  <!--
    Reka Select owns interaction and display in both modes. These inputs only
    preserve native form submission/validation for multi-select arrays.
  -->
  <input
    v-for="value in formHiddenValues"
    :key="value"
    type="hidden"
    :name="props.name"
    :value="value"
  />
  <input
    v-if="props.multiple && props.required"
    type="text"
    :value="multiSelectedValues.length > 0 ? 'has-value' : ''"
    :required="true"
    :disabled="props.disabled"
    tabindex="-1"
    aria-hidden="true"
    class="pointer-events-none absolute m-[-1px] size-px overflow-hidden border-0 p-0 whitespace-nowrap [clip:rect(0,0,0,0)]"
  />
</template>
