import { DateFilter, FilterConfigItem, FilterState, NumberFilter, RelationFilterConfig } from '@types'

export function matchFilter(itemValue: any, filterValues: any[] | undefined): boolean {
  if (!filterValues || filterValues.length === 0) {
    return true
  }
  if (itemValue === null || itemValue === undefined || itemValue === '') {
    return filterValues.includes('')
  }
  const itemString = itemValue instanceof Date ? itemValue.toISOString() : String(itemValue)
  return filterValues.some((f) => String(f) === itemString)
}

export function matchNumberFilter(itemValue: number | null, filter: NumberFilter | undefined): boolean {
  if (!filter) {
    return true
  }
  const { values, min, max } = filter

  // Check range
  if (itemValue == null) {
    // If entry is empty, it fails range check if min or max is set
    if ((min !== undefined && min !== null) || (max !== undefined && max !== null)) {
      return false
    }
  } else {
    if (min !== undefined && min !== null && itemValue < min) {
      return false
    }
    if (max !== undefined && max !== null && itemValue > max) {
      return false
    }
  }

  return matchFilter(itemValue, values)
}

export function matchDateFilter(itemValue: Date | null, filter: DateFilter | undefined): boolean {
  if (!filter) {
    return true
  }
  const { values, start, end } = filter
  const itemString = itemValue?.toISOString() ?? null

  // Check range
  if (itemString) {
    if (start && itemString < start) {
      return false
    }
    if (end && itemString > end) {
      return false
    }
  } else if (start || end) {
    // If entry is empty, it fails range check if start or end is set
    return false
  }

  return matchFilter(itemValue, values)
}

/**
 * Applies range filters for number and date fields.
 */
export function applyRangeFilters<T, TFilters extends FilterState>(
  items: T[],
  accessor: (item: T) => any,
  config: FilterConfigItem<TFilters>,
  filters: TFilters,
): T[] {
  let filteredItems = items
  if (config.valueType === 'number') {
    const filter = filters[config.filterKey] as NumberFilter | undefined
    if (filter) {
      const { min, max } = filter
      if (min !== undefined || max !== undefined) {
        // Apply range filter manually. We only check the range part here, ignoring 'values'
        filteredItems = filteredItems.filter((item) => matchNumberFilter(accessor(item), { min, max }))
      }
    }
  } else if (config.valueType === 'date') {
    const filter = filters[config.filterKey] as DateFilter | undefined
    if (filter) {
      const { start, end } = filter
      if (start !== undefined || end !== undefined) {
        // Apply range filter manually. We only check the range part here, ignoring 'values'
        filteredItems = filteredItems.filter((item) => matchDateFilter(accessor(item), { start, end }))
      }
    }
  }
  return filteredItems
}

/**
 * Collects matches from the filtered items.
 */
export function getMatches<T>(filteredItems: T[], accessor: (item: T) => any): Set<string> {
  const matches = new Set<string>()
  for (const item of filteredItems) {
    const val = accessor(item)
    if (val === null || val === undefined || val === '') {
      matches.add('')
    } else if (val instanceof Date) {
      matches.add(val.toISOString())
    } else {
      matches.add(String(val))
    }
  }
  return matches
}

/**
 * Generates all possible options based on field type.
 */
export function getOptions<T>(
  allItems: T[],
  accessor: (item: T) => any,
  config: FilterConfigItem<any>,
  referencedItems?: Map<string, any>,
): { value: string; label: string }[] {
  if (config.kind === 'relation') {
    return referencedItems ? getRelationOptions(allItems, accessor, config, referencedItems) : []
  }

  if (config.kind === 'enum') {
    return config.enumValues.map((s: string) => ({ value: s, label: s }))
  }

  if (config.kind === 'discriminatedUnion') {
    return config.duMemberTypes.map((m: { type: string; label: string }) => ({ value: m.type, label: m.label }))
  }

  if (config.valueType === 'boolean') {
    return [
      { value: 'true', label: 'Yes' },
      { value: 'false', label: 'No' },
    ]
  }

  return getScalarOptions(allItems, accessor)
}

/**
 * Generates relation options from referenced items.
 */
function getRelationOptions<T>(
  allItems: T[],
  accessor: (item: T) => any,
  config: RelationFilterConfig<any>,
  referencedItems: Map<string, any>,
): { value: string; label: string }[] {
  const options: { value: string; label: string }[] = []
  const usedIds = new Set<any>()

  for (const item of allItems) {
    usedIds.add(accessor(item))
  }

  for (const id of usedIds) {
    if (id === null || id === undefined) {
      options.push({ value: '', label: 'Empty' })
      continue
    }
    const ref = referencedItems.get(id)
    if (ref) {
      const label = String(ref[config.referencedModelLabelField] ?? ref[config.referencedModelIdField])
      options.push({ value: String(ref[config.referencedModelIdField]), label })
    }
  }

  return options
}

/**
 * Generates scalar options from item values.
 */
function getScalarOptions<T>(allItems: T[], accessor: (item: T) => any): { value: string; label: string }[] {
  const values = new Set<string>()
  for (const item of allItems) {
    const raw = accessor(item)
    const normalized = raw instanceof Date ? raw.toISOString() : String(raw ?? '')
    values.add(normalized)
  }

  const options: { value: string; label: string }[] = []
  for (const v of values) {
    options.push({ value: v, label: v === '' ? 'Empty' : v })
  }
  return options
}

/**
 * Formats and sorts the filter options.
 */
export function formatAndSortOptions(
  options: { value: string; label: string }[],
  matches: Set<string>,
): { value: string; label: string; hasMatches: boolean }[] {
  return options
    .map((opt) => ({
      ...opt,
      hasMatches: matches.has(opt.value),
    }))
    .sort((a, b) => {
      if (a.hasMatches && !b.hasMatches) {
        return -1
      }
      if (!a.hasMatches && b.hasMatches) {
        return 1
      }
      if (a.value === '' && b.value !== '') {
        return -1
      }
      if (a.value !== '' && b.value === '') {
        return 1
      }
      return a.label.localeCompare(b.label)
    })
}
