import { useQuery } from '@tanstack/react-query'
import { DateFilter, FilterConfig, FilterFieldName, FilterState, FilterValue, NumberFilter, StringFilter } from '@types'

import { useMemo } from 'react'

import { Slicer } from '@postxl/ui-components'

export const AdminSlicer = <TField extends FilterFieldName, TFilters extends FilterState>({
  field,
  filters,
  onChange,
  config,
  getFilterOptions,
  title,
  optionsHeight,
  className,
}: {
  field: TField
  filters: TFilters
  onChange: (val: FilterValue) => void
  config: FilterConfig<TField, TFilters>
  getFilterOptions: (args: { field: TField; filters: TFilters }) => any
  title?: string
  optionsHeight?: number
  className?: string
}) => {
  const fieldConfig = config[field]
  const { filterKey, valueType: fieldType } = fieldConfig

  // Exclude the current field's values from the query to prevent self-filtering
  // (options list shouldn't filter itself when selecting items).
  // However, keep other filter properties like min/max, contains, etc. so the backend
  // can gray out options that don't match those constraints.
  const queryFilters = useMemo(() => {
    const key = filterKey
    if (!key) {
      return filters
    }

    const currentFilter = filters[key]

    // For number filters, keep min/max but exclude values
    if (fieldType === 'number') {
      const val = currentFilter as NumberFilter | undefined
      if (val && (val.min !== undefined || val.max !== undefined)) {
        return {
          ...filters,
          [key]: { min: val.min, max: val.max },
        }
      }
    }

    // For date filters, keep start/end but exclude values
    if (fieldType === 'date') {
      const val = currentFilter as DateFilter | undefined
      if (val && (val.start !== undefined || val.end !== undefined)) {
        return {
          ...filters,
          [key]: { start: val.start, end: val.end },
        }
      }
    }

    // For string filters, keep text filters but exclude values
    if (fieldType === 'string') {
      const val = currentFilter as StringFilter | undefined
      if (val && (val.contains || val.startsWith || val.endsWith || val.exclude)) {
        return {
          ...filters,
          [key]: {
            contains: val.contains,
            startsWith: val.startsWith,
            endsWith: val.endsWith,
            exclude: val.exclude,
          },
        }
      }
    }

    // Exclude the entire field from the query
    const { [key]: _ignored, ...rest } = filters
    return rest as TFilters
  }, [filterKey, filters, fieldType])

  // Always fetch options (unlike TableFilter which only fetches when open)
  const { data: optionsData, isLoading } = useQuery({
    ...getFilterOptions({ field, filters: queryFilters }),
  })

  const options = useMemo(() => {
    if (!optionsData || !Array.isArray(optionsData)) {
      return []
    }

    return optionsData
      .filter((opt): opt is { value: string; label: string; hasMatches: boolean } => opt != null && 'value' in opt)
      .map((option) => {
        let displayLabel = option.label
        if (option.value !== '') {
          if (fieldType === 'date') {
            displayLabel = new Date(option.label).toLocaleDateString()
          } else if (fieldType === 'number') {
            displayLabel = Number(option.label).toLocaleString()
          }
        }
        return { ...option, label: displayLabel }
      })
  }, [optionsData, fieldType])

  // Check if this field uses StringFilter
  const isStringFilter = fieldType === 'string'

  // Get currently selected values from the filter state
  const selectedValues = useMemo(() => {
    const key = fieldConfig.filterKey
    const val = filters[key]
    if (!val) {
      return new Set<string>()
    }
    if (fieldType === 'number') {
      return new Set(((val as NumberFilter).values || []).map(String))
    }
    if (fieldType === 'date') {
      return new Set((val as DateFilter).values || [])
    }
    if (fieldType === 'boolean') {
      return new Set((val as boolean[]).map(String))
    }
    if (isStringFilter) {
      return new Set(((val as StringFilter).values || []).map(String))
    }
    return new Set(val as string[])
  }, [fieldConfig.filterKey, fieldType, filters, isStringFilter])

  // Handle value changes - preserves other filter properties (min/max, contains, etc.)
  const handleValueChange = (newValues: string[]) => {
    const key = fieldConfig.filterKey
    const currentFilter = filters[key]

    if (fieldType === 'number') {
      const current = currentFilter as NumberFilter | undefined
      const values = newValues.length > 0 ? newValues.map(Number) : undefined
      if (!values && current?.min === undefined && current?.max === undefined) {
        onChange(undefined)
      } else {
        onChange({ min: current?.min, max: current?.max, values })
      }
    } else if (fieldType === 'date') {
      const current = currentFilter as DateFilter | undefined
      const values = newValues.length > 0 ? newValues : undefined
      if (!values && current?.start === undefined && current?.end === undefined) {
        onChange(undefined)
      } else {
        onChange({ start: current?.start, end: current?.end, values })
      }
    } else if (isStringFilter) {
      const current = currentFilter as StringFilter | undefined
      const values = newValues.length > 0 ? newValues : undefined
      if (!values && !current?.contains && !current?.startsWith && !current?.endsWith && !current?.exclude) {
        onChange(undefined)
      } else {
        onChange({
          contains: current?.contains,
          startsWith: current?.startsWith,
          endsWith: current?.endsWith,
          exclude: current?.exclude,
          values,
        })
      }
    } else if (fieldType === 'boolean') {
      onChange(newValues.map((v) => v === 'true'))
    } else {
      onChange(newValues.length > 0 ? newValues : undefined)
    }
  }

  return (
    <Slicer
      filterValues={options}
      selectedValues={selectedValues}
      onChange={handleValueChange}
      title={title || String(field)}
      isLoading={isLoading}
      optionsHeight={optionsHeight}
      className={className}
    />
  )
}
