import { computed, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'

import { useAwardsStore } from '../../stores/awards'
import type { AdminCandidateItem, AdminNominationReviewItem } from '../../types/awards'

const recommendedNominatorsRuleKey = 'recommended_nominators_per_subcategory'

type CategoryStatusFilter = 'all' | 'empty' | 'reviews' | 'thin'

interface CategoryGroupForm {
  groupName: string
  description: string
  sortOrder: number
  maxNomineesPerUser: number
}

interface CategoryWithState {
  id: number
  groupName: string
  name: string
  slug: string
  description: string
  sortOrder: number
  maxNomineesPerUser: number
  viewerRangeMin: number | null
  viewerRangeMax: number | null
  candidateCount: number
  pendingCount: number
  nominationCount: number
  nominatorCount: number
}

export interface AdminSubcategoryDraft {
  name: string
  slug: string
  sortOrder: number
  viewerRangeMin: number | null
  viewerRangeMax: number | null
}

export interface AdminCategoryGroupSummary {
  name: string
  description: string
  sortOrder: number
  maxNomineesPerUser: number
  candidateCount: number
  pendingCount: number
  nominationCount: number
  nominatorCount: number
  categories: CategoryWithState[]
}

export type AdminCategoryOverviewTarget =
  | { type: 'group'; group: AdminCategoryGroupSummary }
  | { type: 'category'; group: AdminCategoryGroupSummary; category: CategoryWithState }

export function useAdminCategoryManager() {
  const store = useAwardsStore()
  const route = useRoute()
  const selectedSeasonId = computed(() => store.adminSelectedSeasonId)
  const seasonDetail = computed(() => store.adminSeasonDetail)

  const query = ref('')
  const statusFilter = ref<CategoryStatusFilter>('all')
  const selectedGroupName = ref<string | null>(null)
  const focusedSubcategory = ref<string | null>(null)
  const saving = ref<string | 'new' | 'templates' | null>(null)
  const adminMessage = ref('')
  const adminError = ref('')
  const groupToDelete = ref<AdminCategoryGroupSummary | null>(null)
  const deleting = ref(false)

  const groupModalOpen = ref(false)
  const editingGroupName = ref<string | null>(null)
  const groupDraft = reactive(createEmptyGroupForm())
  const overviewTarget = ref<AdminCategoryOverviewTarget | null>(null)

  const subcategoryConfigModalOpen = ref(false)
  const subcategoryModalOpen = ref(false)
  const editingSubcategoryIndex = ref<number | null>(null)
  const subcategoryDraft = ref<AdminSubcategoryDraft>(createEmptySubcategoryDraft(1))
  const subcategoryDrafts = ref<AdminSubcategoryDraft[]>([])

  const categoriesWithState = computed<CategoryWithState[]>(() =>
    seasonDetail.value.categories
      .map((category) => {
        const categoryNominations = nominationsForCategory(category.id, seasonDetail.value)
        return {
          ...category,
          candidateCount: category.candidateCount ?? seasonDetail.value.candidates.filter((candidate) => candidate.categoryId === category.id).length,
          pendingCount: seasonDetail.value.pendingNominations.filter((nomination) => nomination.categoryId === category.id).length,
          nominationCount: categoryNominations.length,
          nominatorCount: uniqueNominatorCount(categoryNominations),
        }
      })
      .sort((left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name, 'de')),
  )

  const categoryGroups = computed<AdminCategoryGroupSummary[]>(() =>
    [...groupByName(categoriesWithState.value).values()]
      .map((categories) => {
        const sorted = [...categories].sort((left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name, 'de'))
        const first = sorted[0]
        return {
          name: first.groupName.trim(),
          description: first.description,
          sortOrder: sorted[0]?.sortOrder ?? 1,
          maxNomineesPerUser: first.maxNomineesPerUser,
          candidateCount: sorted.reduce((sum, category) => sum + category.candidateCount, 0),
          pendingCount: sorted.reduce((sum, category) => sum + category.pendingCount, 0),
          nominationCount: sorted.reduce((sum, category) => sum + category.nominationCount, 0),
          nominatorCount: uniqueNominatorCount(sorted.flatMap((category) => nominationsForCategory(category.id, seasonDetail.value))),
          categories: sorted,
        }
      })
      .sort((left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name, 'de')),
  )

  const filteredGroups = computed(() => {
    const search = query.value.trim().toLowerCase()
    return categoryGroups.value.filter((group) => {
      const matchesStatus =
        statusFilter.value === 'all'
        || (statusFilter.value === 'empty' && group.categories.some((category) => category.candidateCount === 0))
        || (statusFilter.value === 'reviews' && group.pendingCount > 0)
        || (statusFilter.value === 'thin' && group.categories.some((category) => category.candidateCount > 0 && category.candidateCount < Math.max(2, category.maxNomineesPerUser)))
      const matchesSearch = !search || [
        group.name,
        group.description,
        ...group.categories.flatMap((category) => [category.name, category.slug, formatViewerRange(category.viewerRangeMin, category.viewerRangeMax)]),
      ]
        .join(' ')
        .toLowerCase()
        .includes(search)

      return matchesStatus && matchesSearch
    })
  })

  const selectedGroup = computed(() =>
    filteredGroups.value.find((group) => group.name === selectedGroupName.value)
    ?? filteredGroups.value[0]
    ?? null,
  )

  const categoryStats = computed(() => [
    { label: 'Hauptkategorien', value: categoryGroups.value.length },
    { label: 'Unterkategorien', value: seasonDetail.value.categories.length },
    { label: 'Kandidaten', value: seasonDetail.value.candidates.length },
    { label: 'Reviews', value: seasonDetail.value.pendingNominations.length },
  ])
  const recommendedNominatorTarget = computed(() => {
    const configuredRule = store.adminWorkflowRules.rules.find((rule) => rule.key === recommendedNominatorsRuleKey)
    if (!configuredRule) {
      return 4
    }

    return configuredRule.enabled ? Math.max(1, configuredRule.limit) : null
  })

  const statusFilters = computed(() => [
    { key: 'all' as const, label: 'Alle', count: categoryGroups.value.length },
    { key: 'empty' as const, label: 'Mit Lücken', count: categoryGroups.value.filter((group) => group.categories.some((category) => category.candidateCount === 0)).length },
    { key: 'reviews' as const, label: 'Mit Reviews', count: categoryGroups.value.filter((group) => group.pendingCount > 0).length },
    { key: 'thin' as const, label: 'Dünn besetzt', count: categoryGroups.value.filter((group) => group.categories.some((category) => category.candidateCount > 0 && category.candidateCount < Math.max(2, category.maxNomineesPerUser))).length },
  ])

  const activeSubcategoryTemplates = computed<AdminSubcategoryDraft[]>(() => {
    if (seasonDetail.value.subcategoryTemplates.length > 0) {
      return seasonDetail.value.subcategoryTemplates
        .map((template) => ({
          name: template.name,
          slug: template.slug,
          sortOrder: template.sortOrder,
          viewerRangeMin: template.viewerRangeMin,
          viewerRangeMax: template.viewerRangeMax,
        }))
        .sort((left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name, 'de'))
    }

    const distinct = new Map<string, AdminSubcategoryDraft>()
    for (const category of categoriesWithState.value) {
      const key = [category.name, category.viewerRangeMin ?? '', category.viewerRangeMax ?? ''].join('|')
      if (!distinct.has(key)) {
        distinct.set(key, {
          name: category.name,
          slug: extractTemplateSlug(category.groupName, category.slug),
          sortOrder: distinct.size + 1,
          viewerRangeMin: category.viewerRangeMin,
          viewerRangeMax: category.viewerRangeMax,
        })
      }
    }

    return [...distinct.values()]
  })
  const focusedSubcategoryTemplate = computed(() => {
    if (!focusedSubcategory.value) return null
    const normalizedFocus = normalizeSubcategoryFocus(focusedSubcategory.value)
    return activeSubcategoryTemplates.value.find((template) =>
      normalizeSubcategoryFocus(template.slug) === normalizedFocus
      || normalizeSubcategoryFocus(template.name) === normalizedFocus,
    ) ?? null
  })

  const canSaveSubcategory = computed(() => validateSubcategoryDraft(subcategoryDraft.value) === null)
  const canSaveSubcategoryConfig = computed(() =>
    subcategoryDrafts.value.length > 0
    && subcategoryDrafts.value.every((draft) => validateSubcategoryDraft(draft) === null)
    && hasUniqueValues(subcategoryDrafts.value.map((draft) => draft.name))
    && hasUniqueValues(subcategoryDrafts.value.map((draft) => draft.slug)),
  )
  const canCreateGroup = computed(() => selectedSeasonId.value !== null && activeSubcategoryTemplates.value.length > 0)
  const canSaveGroup = computed(() => validateGroupForm(groupDraft) === null && (editingGroupName.value !== null || canCreateGroup.value))
  const groupModalTitle = computed(() =>
    editingGroupName.value === null ? 'Hauptkategorie anlegen' : 'Hauptkategorie bearbeiten',
  )
  const subcategoryModalTitle = computed(() =>
    editingSubcategoryIndex.value === null ? 'Unterkategorie hinzufügen' : 'Unterkategorie bearbeiten',
  )

  const overviewCategories = computed(() => {
    if (!overviewTarget.value) return []
    return overviewTarget.value.type === 'group'
      ? overviewTarget.value.group.categories
      : [overviewTarget.value.category]
  })
  const overviewCategoryIds = computed(() => new Set(overviewCategories.value.map((category) => category.id)))
  const overviewCandidates = computed<AdminCandidateItem[]>(() =>
    seasonDetail.value.candidates.filter((candidate) => overviewCategoryIds.value.has(candidate.categoryId)),
  )
  const overviewNominations = computed<AdminNominationReviewItem[]>(() => {
    if (!overviewTarget.value) return []
    const groupName = overviewTarget.value.group.name
    return [...seasonDetail.value.pendingNominations, ...seasonDetail.value.reviewedNominations]
      .filter((nomination) =>
        (nomination.categoryId !== null && overviewCategoryIds.value.has(nomination.categoryId))
        || (nomination.suggestedCategoryId !== null && overviewCategoryIds.value.has(nomination.suggestedCategoryId))
        || (overviewTarget.value?.type === 'group'
          && nomination.categoryId === null
          && nomination.categoryGroupName.trim().toLowerCase() === groupName.trim().toLowerCase()),
      )
  })
  const overviewTitle = computed(() => {
    if (!overviewTarget.value) return ''
    return overviewTarget.value.type === 'group'
      ? overviewTarget.value.group.name
      : `${overviewTarget.value.group.name} · ${overviewTarget.value.category.name}`
  })
  const overviewSubtitle = computed(() => {
    if (!overviewTarget.value) return ''
    return overviewTarget.value.type === 'group'
      ? `${overviewCategories.value.length} Unterkategorien · ${overviewCandidates.value.length} Kandidaten · ${overviewNominations.value.length} Nominierungen`
      : `${formatViewerRange(overviewTarget.value.category.viewerRangeMin, overviewTarget.value.category.viewerRangeMax)} · ${overviewCandidates.value.length} Kandidaten · ${overviewNominations.value.length} Nominierungen`
  })

  function applyRouteFilters() {
    const rawQuery = Array.isArray(route.query.q) ? route.query.q[0] : route.query.q
    const rawStatus = Array.isArray(route.query.status) ? route.query.status[0] : route.query.status
    const rawGroup = Array.isArray(route.query.group) ? route.query.group[0] : route.query.group
    const rawSubcategory = Array.isArray(route.query.subcategory) ? route.query.subcategory[0] : route.query.subcategory

    query.value = typeof rawQuery === 'string' ? rawQuery : ''
    statusFilter.value = isCategoryStatusFilter(rawStatus) ? rawStatus : 'all'
    selectedGroupName.value = typeof rawGroup === 'string' && rawGroup.trim() ? rawGroup : null
    focusedSubcategory.value = typeof rawSubcategory === 'string' && rawSubcategory.trim() ? rawSubcategory.trim() : null
  }

  watch(categoryGroups, (groups) => {
    if (!groups.some((group) => group.name === selectedGroupName.value)) {
      selectedGroupName.value = groups[0]?.name ?? null
    }
  }, { immediate: true })

  watch(filteredGroups, (groups) => {
    if (!groups.some((group) => group.name === selectedGroupName.value)) {
      selectedGroupName.value = groups[0]?.name ?? null
    }
  })

  watch(
    () => [route.query.q, route.query.status, route.query.group, route.query.subcategory] as const,
    () => {
      applyRouteFilters()
    },
    { immediate: true },
  )

  function isFocusedSubcategory(nameOrSlug: string) {
    if (!focusedSubcategoryTemplate.value) return false
    const normalizedValue = normalizeSubcategoryFocus(nameOrSlug)
    return normalizedValue === normalizeSubcategoryFocus(focusedSubcategoryTemplate.value.slug)
      || normalizedValue === normalizeSubcategoryFocus(focusedSubcategoryTemplate.value.name)
  }

  function openSubcategoryConfigModal() {
    adminError.value = ''
    subcategoryDrafts.value = activeSubcategoryTemplates.value.map(cloneSubcategoryDraft)
    subcategoryConfigModalOpen.value = true
    subcategoryModalOpen.value = false
    editingSubcategoryIndex.value = null
  }

  function openCreateGroupModal() {
    adminError.value = ''
    editingGroupName.value = null
    Object.assign(groupDraft, createEmptyGroupForm(categoryGroups.value.length + 1))
    groupModalOpen.value = true
  }

  function openEditGroupModal(group: AdminCategoryGroupSummary) {
    adminError.value = ''
    editingGroupName.value = group.name
    Object.assign(groupDraft, createGroupForm(group))
    groupModalOpen.value = true
  }

  function closeGroupModal() {
    groupModalOpen.value = false
    editingGroupName.value = null
    Object.assign(groupDraft, createEmptyGroupForm(categoryGroups.value.length + 1))
  }

  function closeSubcategoryConfigModal() {
    subcategoryConfigModalOpen.value = false
    subcategoryModalOpen.value = false
    editingSubcategoryIndex.value = null
  }

  function openCreateSubcategoryModal() {
    adminError.value = ''
    editingSubcategoryIndex.value = null
    subcategoryDraft.value = createEmptySubcategoryDraft(nextDraftSortOrder(subcategoryDrafts.value))
    subcategoryModalOpen.value = true
  }

  function openEditSubcategoryModal(index: number) {
    const template = subcategoryDrafts.value[index]
    if (!template) return
    adminError.value = ''
    editingSubcategoryIndex.value = index
    subcategoryDraft.value = cloneSubcategoryDraft(template)
    subcategoryModalOpen.value = true
  }

  function closeSubcategoryModal() {
    subcategoryModalOpen.value = false
    editingSubcategoryIndex.value = null
  }

  function fillSubcategorySlug() {
    subcategoryDraft.value.slug = slugify(subcategoryDraft.value.name) || `unterkategorie-${nextDraftSortOrder(subcategoryDrafts.value)}`
  }

  function saveSubcategoryDraft() {
    const validationMessage = validateSubcategoryDraft(subcategoryDraft.value)
    if (validationMessage) {
      adminError.value = validationMessage
      return
    }

    const normalized = normalizeDraft(subcategoryDraft.value)
    if (editingSubcategoryIndex.value === null) {
      subcategoryDrafts.value = [...subcategoryDrafts.value, normalized]
    } else {
      subcategoryDrafts.value = subcategoryDrafts.value.map((item, index) => index === editingSubcategoryIndex.value ? normalized : item)
    }

    closeSubcategoryModal()
  }

  function removeDraftSubcategory(index: number) {
    subcategoryDrafts.value = subcategoryDrafts.value
      .filter((_, itemIndex) => itemIndex !== index)
      .map((draft, itemIndex) => ({ ...draft, sortOrder: itemIndex + 1 }))
  }

  async function saveSubcategoryConfig() {
    if (!selectedSeasonId.value || !canSaveSubcategoryConfig.value) return

    saving.value = 'templates'
    adminMessage.value = ''
    adminError.value = ''

    try {
      await store.updateAdminSeasonSubcategoryTemplates(selectedSeasonId.value, {
        templates: subcategoryDrafts.value
          .map(normalizeDraft)
          .sort((left, right) => left.sortOrder - right.sortOrder)
          .map((draft, index) => ({ ...draft, sortOrder: index + 1 })),
      })
      adminMessage.value = 'Unterkategorien wurden für alle Hauptkategorien aktualisiert.'
      closeSubcategoryConfigModal()
    } catch (error) {
      adminError.value = error instanceof Error ? error.message : 'Unterkategorien konnten nicht gespeichert werden.'
    } finally {
      saving.value = null
    }
  }

  async function saveGroupModal() {
    if (!selectedSeasonId.value || !canSaveGroup.value) return

    const targetGroupName = editingGroupName.value
    saving.value = targetGroupName ?? 'new'
    adminMessage.value = ''
    adminError.value = ''

    try {
      const payload = {
        groupName: groupDraft.groupName.trim(),
        description: groupDraft.description.trim(),
        sortOrder: normalizePositiveInteger(groupDraft.sortOrder, 1),
        maxNomineesPerUser: normalizePositiveInteger(groupDraft.maxNomineesPerUser, 3),
      }
      if (targetGroupName) {
        await store.updateAdminCategoryGroup(selectedSeasonId.value, targetGroupName, payload)
        adminMessage.value = 'Hauptkategorie gespeichert.'
      } else {
        await store.createAdminCategoryGroup(selectedSeasonId.value, payload)
        adminMessage.value = 'Hauptkategorie angelegt.'
      }
      selectedGroupName.value = payload.groupName
      closeGroupModal()
    } catch (error) {
      adminError.value = error instanceof Error ? error.message : targetGroupName
        ? 'Hauptkategorie konnte nicht gespeichert werden.'
        : 'Hauptkategorie konnte nicht angelegt werden.'
    } finally {
      saving.value = null
    }
  }

  async function confirmDeleteGroup() {
    if (!selectedSeasonId.value || !groupToDelete.value) return

    deleting.value = true
    adminError.value = ''
    try {
      await store.deleteAdminCategoryGroup(selectedSeasonId.value, groupToDelete.value.name)
      adminMessage.value = `Hauptkategorie "${groupToDelete.value.name}" wurde geloescht.`
      groupToDelete.value = null
    } catch (error) {
      adminError.value = error instanceof Error ? error.message : 'Loeschen fehlgeschlagen.'
    } finally {
      deleting.value = false
    }
  }

  function openGroupOverview(group: AdminCategoryGroupSummary) {
    overviewTarget.value = { type: 'group', group }
  }

  function openCategoryOverview(group: AdminCategoryGroupSummary, category: CategoryWithState) {
    overviewTarget.value = { type: 'category', group, category }
  }

  return {
    selectedSeasonId,
    query,
    statusFilter,
    selectedGroupName,
    focusedSubcategory,
    saving,
    adminMessage,
    adminError,
    groupModalOpen,
    editingGroupName,
    groupDraft,
    overviewTarget,
    overviewTitle,
    overviewSubtitle,
    overviewCategories,
    overviewCandidates,
    overviewNominations,
    filteredGroups,
    selectedGroup,
    categoryStats,
    statusFilters,
    activeSubcategoryTemplates,
    focusedSubcategoryTemplate,
    subcategoryConfigModalOpen,
    subcategoryModalOpen,
    subcategoryDraft,
    subcategoryDrafts,
    subcategoryModalTitle,
    canSaveSubcategory,
    canSaveSubcategoryConfig,
    canCreateGroup,
    canSaveGroup,
    groupModalTitle,
    groupToDelete,
    deleting,
    openCreateGroupModal,
    openEditGroupModal,
    closeGroupModal,
    openSubcategoryConfigModal,
    closeSubcategoryConfigModal,
    openCreateSubcategoryModal,
    openEditSubcategoryModal,
    closeSubcategoryModal,
    fillSubcategorySlug,
    saveSubcategoryDraft,
    removeDraftSubcategory,
    saveSubcategoryConfig,
    saveGroupModal,
    confirmDeleteGroup,
    openGroupOverview,
    openCategoryOverview,
    isFocusedSubcategory,
    formatViewerRange,
    recommendedNominatorTarget,
  }
}

function groupByName(categories: CategoryWithState[]) {
  const groups = new Map<string, CategoryWithState[]>()
  for (const category of categories) {
    const key = category.groupName.trim() || 'Ohne Gruppe'
    const current = groups.get(key) ?? []
    current.push(category)
    groups.set(key, current)
  }

  return groups
}

function nominationsForCategory(categoryId: number, source?: {
  pendingNominations: AdminNominationReviewItem[]
  reviewedNominations: AdminNominationReviewItem[]
}) {
  const allNominations = source
    ? [...source.pendingNominations, ...source.reviewedNominations]
    : []

  if (source) {
    return allNominations.filter((nomination) =>
      (nomination.categoryId !== null && nomination.categoryId === categoryId)
      || (nomination.suggestedCategoryId !== null && nomination.suggestedCategoryId === categoryId),
    )
  }

  return []
}

function uniqueNominatorCount(nominations: AdminNominationReviewItem[]) {
  return new Set(nominations.map((nomination) => nomination.submittedByTwitchId.trim().toLowerCase()).filter(Boolean)).size
}

function isCategoryStatusFilter(value: unknown): value is CategoryStatusFilter {
  return value === 'all' || value === 'empty' || value === 'reviews' || value === 'thin'
}

function normalizeSubcategoryFocus(value: string) {
  return value.trim().toLowerCase()
}

function createGroupForm(group?: AdminCategoryGroupSummary, sortOrder = 1): CategoryGroupForm {
  return {
    groupName: group?.name ?? '',
    description: group?.description ?? '',
    sortOrder: group?.sortOrder ?? sortOrder,
    maxNomineesPerUser: group?.maxNomineesPerUser ?? 3,
  }
}

function createEmptyGroupForm(sortOrder = 1): CategoryGroupForm {
  return createGroupForm(undefined, sortOrder)
}

function validateGroupForm(form: CategoryGroupForm) {
  if (!form.groupName.trim()) return 'Der Name der Hauptkategorie ist erforderlich.'
  if (form.groupName.trim().length > 80) return 'Der Name der Hauptkategorie muss unter 80 Zeichen bleiben.'
  if (form.description.trim().length > 400) return 'Die Beschreibung muss unter 400 Zeichen bleiben.'
  if (normalizePositiveInteger(form.sortOrder, 1) > 200) return 'Die Reihenfolge muss zwischen 1 und 200 liegen.'
  if (normalizePositiveInteger(form.maxNomineesPerUser, 3) > 10) return 'Das Nominierungs-Limit muss zwischen 1 und 10 liegen.'
  return null
}

function normalizePositiveInteger(value: number, fallback: number) {
  return Number.isFinite(value) ? Math.max(1, Math.round(value)) : fallback
}

function createEmptySubcategoryDraft(sortOrder: number): AdminSubcategoryDraft {
  return {
    name: '',
    slug: `unterkategorie-${sortOrder}`,
    sortOrder,
    viewerRangeMin: null,
    viewerRangeMax: null,
  }
}

function cloneSubcategoryDraft(draft: AdminSubcategoryDraft): AdminSubcategoryDraft {
  return {
    name: draft.name,
    slug: draft.slug,
    sortOrder: draft.sortOrder,
    viewerRangeMin: draft.viewerRangeMin,
    viewerRangeMax: draft.viewerRangeMax,
  }
}

function nextDraftSortOrder(items: AdminSubcategoryDraft[]) {
  return items.reduce((max, item) => Math.max(max, item.sortOrder), 0) + 1
}

function validateSubcategoryDraft(draft: AdminSubcategoryDraft) {
  const normalized = normalizeDraft(draft)
  if (!normalized.name) return 'Der Name der Unterkategorie ist erforderlich.'
  if (!normalized.slug) return 'Der Slug der Unterkategorie ist erforderlich.'
  if (normalized.viewerRangeMin !== null && normalized.viewerRangeMin < 0) return 'Viewer-Range darf nicht negativ sein.'
  if (normalized.viewerRangeMax !== null && normalized.viewerRangeMax < 0) return 'Viewer-Range darf nicht negativ sein.'
  if (normalized.viewerRangeMin !== null && normalized.viewerRangeMax !== null && normalized.viewerRangeMax < normalized.viewerRangeMin) {
    return 'Viewer-Range Ende muss groesser oder gleich dem Start sein.'
  }

  return null
}

function normalizeDraft(draft: AdminSubcategoryDraft): AdminSubcategoryDraft {
  return {
    name: draft.name.trim(),
    slug: slugify(draft.slug) || slugify(draft.name),
    sortOrder: Number.isFinite(draft.sortOrder) ? Math.max(1, Math.round(draft.sortOrder)) : 1,
    viewerRangeMin: normalizeNullableNumber(draft.viewerRangeMin),
    viewerRangeMax: normalizeNullableNumber(draft.viewerRangeMax),
  }
}

function normalizeNullableNumber(value: number | null) {
  return value === null || Number.isNaN(value) ? null : Math.max(0, Math.round(value))
}

function hasUniqueValues(values: string[]) {
  const normalized = values.map((value) => value.trim().toLowerCase()).filter(Boolean)
  return new Set(normalized).size === normalized.length
}

function extractTemplateSlug(groupName: string, slug: string) {
  const groupSlug = slugify(groupName)
  const normalizedSlug = slug.trim().toLowerCase()
  const prefix = groupSlug ? `${groupSlug}-` : ''
  return prefix && normalizedSlug.startsWith(prefix) ? normalizedSlug.slice(prefix.length) : normalizedSlug
}

function slugify(value: string) {
  return value
    .trim()
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
}

export function formatViewerRange(min: number | null, max: number | null) {
  if (min !== null && max !== null) return `${min} - ${max} Viewer`
  if (min !== null) return `ab ${min} Viewer`
  if (max !== null) return `bis ${max} Viewer`
  return 'ohne Viewer-Range'
}
