<script setup lang="ts">
import {
  computed,
  provide,
  toRef,
  useSlots,
  type Slots,
  type VNode,
} from 'vue';
import { Field as ShadcnField } from '@/components/core/field';
import type { UiFieldProps } from './types';
import { FIELD_DESCRIPTION_CHECKBOX_KEY } from './keys';

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

const props = withDefaults(defineProps<UiFieldProps>(), {
  orientation: 'vertical',
  descriptionPlacement: 'under-input',
  descriptionCheckbox: false,
});

provide(FIELD_DESCRIPTION_CHECKBOX_KEY, toRef(props, 'descriptionCheckbox'));

const slots: Slots = useSlots();

const isResponsive = computed(() => props.orientation === 'responsive');

const shouldGroupLabel = computed(
  () => isResponsive.value && props.descriptionPlacement === 'under-label',
);

const shouldGroupInput = computed(
  () => isResponsive.value && props.descriptionPlacement === 'under-input',
);

const placementClass = computed(() => {
  if (isResponsive.value) return '';
  return props.descriptionPlacement === 'under-label'
    ? '[&>[data-slot=field-description]]:order-1 [&>[data-slot=field-content]]:order-2'
    : '[&>[data-slot=field-content]]:order-1 [&>[data-slot=field-description]]:order-2';
});

function getSlotName(vnode: VNode): string | undefined {
  return (vnode.type as Record<string, unknown>)?.name as string | undefined;
}

function partitionSlots(groupNames: string[]) {
  const children = slots.default?.({}) ?? [];
  const group = children.filter((v) =>
    groupNames.includes(getSlotName(v) ?? ''),
  );
  const rest = children.filter(
    (v) => !groupNames.includes(getSlotName(v) ?? ''),
  );
  return { group, rest };
}

const labelPartition = computed(() =>
  partitionSlots(['UiFieldLabel', 'UiFieldDescription']),
);

const inputPartition = computed(() => {
  const children = slots.default?.({}) ?? [];
  const content = children.filter((v) => getSlotName(v) === 'UiFieldContent');
  const desc = children.filter((v) => getSlotName(v) === 'UiFieldDescription');
  const rest = children.filter((v) => {
    const n = getSlotName(v);
    return n !== 'UiFieldContent' && n !== 'UiFieldDescription';
  });
  return { group: [...content, ...desc], rest };
});

// Stable function refs to avoid unmount/remount on every render.
// Inline arrows in <component :is="() => ..."> create new references each
// render cycle, causing Vue to treat them as different components.
function renderLabelGroup() {
  return labelPartition.value.group;
}
function renderLabelRest() {
  return labelPartition.value.rest;
}
function renderInputRest() {
  return inputPartition.value.rest;
}
function renderInputGroup() {
  return inputPartition.value.group;
}
</script>

<template>
  <ShadcnField
    :orientation="props.orientation"
    :class="placementClass"
    :data-description-placement="props.descriptionPlacement"
  >
    <template v-if="shouldGroupLabel">
      <div class="flex flex-col gap-1.5">
        <component :is="renderLabelGroup" />
      </div>
      <component :is="renderLabelRest" />
    </template>

    <template v-else-if="shouldGroupInput">
      <component :is="renderInputRest" />
      <div class="flex flex-1 flex-col gap-1.5">
        <component :is="renderInputGroup" />
      </div>
    </template>

    <template v-else>
      <slot />
    </template>
  </ShadcnField>
</template>
