<template>
  <ol
    ref="orderFieldListEl"
    class="order-filter tw-flex tw-flex-col tw-gap-3 tw-p-0 tw-m-0 tw-w-min group"
  >
    <li
      v-for="(sortOrders, sortField) in normalizedChoices"
      :key="sortField"
      class="tw-flex tw-gap-3 tw-items-center tw-transition-opacity group-hover:tw-opacity-100"
      :class="{ 'tw-opacity-50': !selectedFieldNames.includes(sortField) }"
    >
      <button
        type="button"
        class="btn tw-cursor-grab tw-p-1 -tw-ml-1"
        :class="{ 'tw-invisible': !selectedFieldNames.includes(sortField) }"
        tabindex="-1"
        data-drag-handle
      >
        <icon-system-uicons-drag-vertical />
      </button>
      <label
        class="btn tw-my-0 -tw-ml-3 tw-mr-6 tw-whitespace-nowrap tw-gap-3 hocus:tw-bg-gray-100"
      >
        <input
          type="checkbox"
          :checked="selectedFieldNames.includes(sortField)"
          @input="
            handleOrderSelection(
              sortField,
              null,
              ($event?.target as HTMLInputElement)?.checked === false,
            )
          "
        />
        <span>{{ t(`${translateBase}.${sortField}.name`) }}</span>
      </label>
      <RadioGroup
        class="tw-whitespace-nowrap tw-ml-auto"
        :name="sortField"
        :choices="sortOrders"
        :model-value="selectedOrder[sortField] ?? null"
        @update:model-value="handleOrderSelection(sortField, $event)"
      />
    </li>
  </ol>
</template>

<script lang="ts" setup generic="T extends string">
import { useSortable } from '@vueuse/integrations/useSortable'
import { computed, ref } from 'vue'
import { useI18n } from '@/i18n'
import RadioGroup from '@/components/generic/RadioGroup.vue'

defineOptions({ compatConfig: { MODE: 3 } })

type OrderDirection = 'asc' | 'desc'
type OrderField = { name: T; directions: OrderDirection[] }
type Value = T | `-${T}`
const modelValue = defineModel<Value[]>({ required: true })
const props = defineProps<{
  choices: (T | OrderField)[]
  translateBase: string
}>()
const { t } = useI18n()
const orderFieldListEl = ref<HTMLOListElement>()
const orderFields = computed<OrderField[]>(() => {
  return props.choices.map((choice) => normalizeOrderField(choice))
})
const selectedFieldNames = computed(
  () => modelValue.value.map((value) => (value.startsWith('-') ? value.slice(1) : value)) as T[],
)
const deselectedFieldNames = computed(
  () =>
    orderFields.value
      .map((field) => field.name)
      .filter((fieldName) => !selectedFieldNames.value.includes(fieldName)) as T[],
)
useSortable(orderFieldListEl, modelValue, {
  animation: 200,
  ghostClass: 'order-filter-drag-ghost',
  handle: '[data-drag-handle]',
})

const normalizedChoices = computed(() => {
  const orderedFieldNames = [...selectedFieldNames.value, ...deselectedFieldNames.value] as T[]
  const orderedOrderFields = orderedFieldNames.map(
    (fieldName) => orderFields.value.find((field) => field.name === fieldName) as OrderField,
  )

  return Object.fromEntries(
    orderedOrderFields.map((field) => {
      return [
        field.name,
        field.directions.map((direction) => ({
          value: direction,
          label: createChoiceLabel(field.name, direction),
        })),
      ]
    }),
  ) as Record<T, { value: OrderDirection; label: string }[]>
})

const selectedOrder = computed(
  () =>
    Object.fromEntries(
      modelValue.value.map((value) => {
        const field = (value.startsWith('-') ? value.slice(1) : value) as T
        return [field, value.startsWith('-') ? 'desc' : 'asc']
      }),
    ) as Record<T, OrderDirection>,
)

function handleOrderSelection(fieldName: T, direction: OrderDirection | null, forceOff = false) {
  const currentValue = [...modelValue.value]
  const newSortDirection =
    direction ?? orderFields.value.find((f) => f.name === fieldName)?.directions[0] ?? 'asc'
  const newSortField: Value = newSortDirection === 'asc' ? fieldName : `-${fieldName}`

  let includedFieldName: Value | null = null
  if (currentValue.includes(fieldName)) {
    includedFieldName = fieldName
  } else if (currentValue.includes(`-${fieldName}`)) {
    includedFieldName = `-${fieldName}`
  }

  if (includedFieldName === null) {
    // Field is currently not active as sorting parameter. Activating now.
    currentValue.push(newSortField)
  } else {
    // Field is currently active as sorting parameter.
    // The field may be active, but the sort direction might have changed.
    // Check if we have to replace the current sorting field or have to remove it.
    const replaceValue = includedFieldName !== newSortField && !forceOff ? [newSortField] : []
    currentValue.splice(currentValue.indexOf(includedFieldName), 1, ...replaceValue)
  }

  modelValue.value = currentValue
}

function createChoiceLabel(fieldName: T, direction: OrderDirection) {
  if (direction === 'asc') return t(`${props.translateBase}.${fieldName}.directions.asc`)
  if (direction === 'desc') return t(`${props.translateBase}.${fieldName}.directions.desc`)
}

function normalizeOrderField(fieldName: T | OrderField): OrderField {
  if (typeof fieldName === 'string') return { name: fieldName, directions: ['asc', 'desc'] }
  else return fieldName
}
</script>

<style scoped lang="postcss">
.order-filter-drag-ghost {
  @apply tw-rounded;
  @apply tw-opacity-40;
  @apply tw-border-2;
  @apply tw-border-solid;
  @apply tw-border-aura-primary;
}

.order-filter :deep([role='radiogroup'] .btn) {
  @apply tw-min-w-[90px];
}
</style>