import moment, { type Moment } from 'moment'
import { type ColumnInterface, type ComputedColumnInterface } from '../DefaultTableInterfaces'
import {
  isCalendarHeaderInterface,
  isDefaultSelectHeaderInterface,
  isEnumSelectHeaderInterface,
  isMultipleDefaultSelectHeaderInterface,
  isMultipleEnumSelectHeaderInterface,
  isMultipleParamsRelationSelectHeaderInterface,
  isMultipleRelationSelectHeaderInterface,
  isNumberSearchHeaderInterface,
  isPeriodCalendarHeaderInterface,
  isRangeNumberHeaderInterface,
  isRelationSelectHeaderInterface,
  isTextSearchHeaderInterface,
} from '../headers/HeadersTableInterfaces'
import {
  isSerializedCalendarTableFilter,
  isSerializedDefaultSelectTableFilter,
  isSerializedEnumSelectTableFilter,
  isSerializedMultipleDefaultSelectTableFilter,
  isSerializedMultipleEnumSelectTableFilter,
  isSerializedMultipleParamsRelationSelectTableFilter,
  isSerializedMultipleRelationSelectTableFilter,
  isSerializedNumberTableFilter,
  isSerializedPeriodCalendarTableFilter,
  isSerializedRangeNumberTableFilter,
  isSerializedRelationSelectTableFilter,
  isSerializedTextTableFilter,
  type SerializedCalendarTableFilter,
  type SerializedDefaultSelectTableFilter,
  type SerializedEnumSelectTableFilter,
  type SerializedMultipleDefaultSelectTableFilter,
  type SerializedMultipleEnumSelectTableFilter,
  type SerializedMultipleParamsRelationSelectTableFilter,
  type SerializedMultipleRelationSelectTableFilter,
  type SerializedNumberTableFilter,
  type SerializedPeriodCalendarTableFilter,
  type SerializedRangeNumberTableFilter,
  type SerializedRelationSelectTableFilter,
  type SerializedTableFilter,
  type SerializedTextTableFilter,
} from './FilterInterfaces'

export interface FilterData {
  code: string
  value: string | string[]
  column?: ComputedColumnInterface
}

export type FilterValue = string | { id: string, name: string }

// export abstract class TableFilter {
//   isVisible = false

//   abstract fromColumn(c: ColumnInterface): void

//   abstract addValue(value: any): void

//   abstract removeValue(value: any): void

//   abstract removeValues(): void

//   abstract hasValues(): boolean

//   abstract getValues(): any[]

//   abstract showValues(): string

//   abstract getQueryParams(): { [code: string]: string | string[] | number }

//   serialize(): SerializedTableFilter {
//     return {
//       class: 'TableFilter',
//       isVisible: this.isVisible,
//     }
//   }

//   deserialize(data: SerializedTableFilter): TableFilter {
//     this.isVisible = data.isVisible

//     return this
//   }
// }

export class TableFilter {
  isVisible = false

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  fromColumn(c: ColumnInterface) {}

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  addValue(value: any) {}

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  removeValue(value: any) {}

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  removeValues() {}

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  hasValues() {
    return false
  }

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  getValues(): any[] {
    return []
  }

  // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
  showValues(): string {
    return ''
  }

  serialize(): SerializedTableFilter {
    return {
      class: 'TableFilter',
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedTableFilter): TableFilter {
    this.isVisible = data.isVisible

    return this
  }

  // eslint-disable-next-line class-methods-use-this
  getQueryParams(): { [code: string]: string | number | Array<string | number> } {
    return {}
  }
}

export class TextTableFilter extends TableFilter {
  value = ''

  searchParam!: string

  fromColumn(c: ColumnInterface) {
    if (!isTextSearchHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать TextTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.searchParam = c.header.searchParam

    return this
  }

  addValue(value: FilterValue) {
    if (typeof value !== 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = ''
    this.isVisible = this.hasValues()
  }

  removeValues() {
    this.value = ''
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== ''
  }

  getValues() {
    return [this.value]
  }

  showValues() {
    return String([this.value].length)
  }

  serialize(): SerializedTextTableFilter {
    return {
      class: 'TextTableFilter',
      value: this.value,
      isVisible: this.isVisible,
      searchParam: this.searchParam,
    }
  }

  deserialize(data: SerializedTextTableFilter): TextTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.isVisible = data.isVisible
    this.searchParam = data.searchParam

    return this
  }

  getQueryParams(): { [code: string]: string | string[] } {
    const result: { [code: string]: string | string[] } = {}

    if (this.hasValues()) {
      result[this.searchParam] = this.value
    }

    return result
  }
}

export class NumberTableFilter extends TableFilter {
  value: number | undefined = undefined

  searchParam!: string

  fromColumn(c: ColumnInterface) {
    if (!isNumberSearchHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать NumberTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.searchParam = c.header.searchParam

    return this
  }

  addValue(val: string | number | undefined) {
    const value = Number(val)

    if (typeof value !== 'number') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  removeValues() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== undefined
  }

  getValues() {
    return [this.value]
  }

  showValues() {
    return String(this.value)
  }

  serialize(): SerializedNumberTableFilter {
    return {
      class: 'NumberTableFilter',
      value: this.value,
      isVisible: this.isVisible,
      searchParam: this.searchParam,
    }
  }

  deserialize(data: SerializedNumberTableFilter): NumberTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.isVisible = data.isVisible
    this.searchParam = data.searchParam

    return this
  }

  getQueryParams(): { [code: string]: string | string[] } {
    const result: { [code: string]: string | string[] } = {}

    if (this.hasValues()) {
      result[this.searchParam] = String(this.value)
    }

    return result
  }
}

export class RangeNumberTableFilter extends TableFilter {
  from: number | undefined = undefined

  to: number | undefined = undefined

  fromParam!: string

  toParam!: string

  defaultValue?: { from?: number, to?: number }

  serializationType!: 'default' | 'arkspace'

  arkspaceSerializationTypeParam?: string

  fromColumn(c: ColumnInterface) {
    if (!isRangeNumberHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать RangeNumberTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.fromParam = c.header.fromParam
    this.toParam = c.header.toParam
    this.serializationType = c.header.serializationType ?? 'default'
    this.arkspaceSerializationTypeParam = c.header.arkspaceSerializationTypeParam

    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : {}
      this.addValue(c.header.defaultValue)
    }

    return this
  }

  addValue(value: { from?: number | undefined, to?: number | undefined }) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.from = value.from
    this.to = value.to
    this.isVisible = this.hasValues()
  }

  removeValue(val: number) {
    if (this.from === val) {
      this.from = undefined
    } else if (this.to === val) {
      this.to = undefined
    }

    this.isVisible = this.hasValues()
  }

  removeValues() {
    this.addValue(this.defaultValue ?? {})
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.from !== undefined || this.to !== undefined
  }

  getValues() {
    const result: number[] = []

    if (this.from !== undefined) {
      result.push(this.from)
    }

    if (this.to !== undefined) {
      result.push(this.to)
    }

    return result
  }

  showValues() {
    let result = ''

    if (this.from !== undefined) {
      result += `от ${this.from}`
    }

    if (this.to !== undefined) {
      result += ` до ${this.to}`
    }

    return result
  }

  serialize(): SerializedRangeNumberTableFilter {
    return {
      class: 'RangeNumberTableFilter',
      isVisible: this.isVisible,
      from: this.from,
      to: this.to,
      fromParam: this.fromParam,
      toParam: this.toParam,
      serializationType: this.serializationType,
      arkspaceSerializationTypeParam: this.arkspaceSerializationTypeParam,
    }
  }

  deserialize(data: SerializedRangeNumberTableFilter): RangeNumberTableFilter {
    super.deserialize(data)

    this.from = data.from
    this.to = data.to
    this.fromParam = data.fromParam
    this.toParam = data.toParam
    this.isVisible = data.isVisible
    this.serializationType = data.serializationType
    this.arkspaceSerializationTypeParam = data.arkspaceSerializationTypeParam

    return this
  }

  getQueryParams(): { [code: string]: number | Array<string | number> } {
    if (!this.hasValues()) return {}
    const result: { [code: string]: number | Array<string | number> } = {}

    switch (this.serializationType) {
      case 'arkspace':
        if (!this.arkspaceSerializationTypeParam) {
          throw new Error('Параметр arkspaceSerializationTypeParam не задан для NumberRange')
        }
        result[this.arkspaceSerializationTypeParam] = [
          typeof this.from === 'number' ? this.from : 'none',
          typeof this.to === 'number' ? this.to : 'none',
        ]

        break
      default:
        if (this.from !== undefined) {
          result[this.fromParam] = this.from
        }

        if (this.to !== undefined) {
          result[this.toParam] = this.to
        }
    }

    return result
  }
}

/**
 * * Calendars
 */

export class CalendarTableFilter extends TableFilter {
  dateParam!: string

  date: Moment | null = null

  formatForDisplay = 'DD.MM.YYYY'

  defaultValue?: Moment

  fromColumn(c: ColumnInterface) {
    if (!isCalendarHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать CalendarTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.dateParam = c.header.dateParam
    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : undefined
      this.date = c.header.defaultValue
    }

    return this
  }

  addValue(value: Moment | null) {
    if (typeof value !== 'object') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.date = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.date = null
    this.isVisible = this.hasValues()
  }

  removeValues() {
    if (this.defaultValue) this.date = this.defaultValue
    else this.date = null
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.date !== null
  }

  getValues() {
    const result: string[] = []

    if (this.date) {
      result.push(this.date.format(this.formatForDisplay))
    }

    return result
  }

  showValues() {
    let result = ''

    if (this.date) {
      result = this.date.format(this.formatForDisplay)
    }

    return result
  }

  serialize(): SerializedCalendarTableFilter {
    return {
      class: 'CalendarTableFilter',
      date: this.date?.toString(),
      dateParam: this.dateParam,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedCalendarTableFilter): CalendarTableFilter {
    super.deserialize(data)

    this.date = data.date ? moment(data.date) : null
    this.dateParam = data.dateParam
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string } {
    const result: { [code: string]: string } = {}

    if (this.date) {
      result[this.dateParam] = this.date.format('YYYY-MM-DD')
    }

    return result
  }
}

export class PeriodCalendarTableFilter extends TableFilter {
  isSetOnlyFullDate!: boolean

  endDateParam!: string

  startDateParam!: string

  arkspaceSerializationTypeParam?: string

  formatForDisplay = 'DD.MM.YYYY'

  endDate: Moment | undefined = undefined

  startDate: Moment | undefined = undefined

  serializationType!: 'default' | 'arkspace'

  defaultValue?: { startDate?: Moment, endDate?: Moment }

  fromColumn(c: ColumnInterface) {
    if (!isPeriodCalendarHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать PeriodCalendarTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)

    this.endDateParam = c.header.endDateParam
    this.startDateParam = c.header.startDateParam
    this.arkspaceSerializationTypeParam = c.header.arkspaceSerializationTypeParam
    this.isSetOnlyFullDate = c.header.isSetOnlyFullDate ?? false
    this.serializationType = c.header.serializationType ?? 'default'

    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : {}
      this.addValue(c.header.defaultValue)
    }

    return this
  }

  addValue(value: { startDate?: Moment | undefined, endDate?: Moment | undefined }) {
    if (typeof value !== 'object') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.startDate = value.startDate
    this.endDate = value.endDate
    this.isVisible = this.hasValues()
  }

  removeValue(value: string) {
    const date: Moment = moment(value, this.formatForDisplay)

    if (this.startDate?.isSame(date)) {
      this.startDate = undefined
    } else if (this.endDate?.isSame(date)) {
      this.endDate = undefined
    }

    this.isVisible = this.hasValues()
  }

  removeValues() {
    this.addValue(this.defaultValue ?? {})
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.startDate !== undefined || this.endDate !== undefined
  }

  getValues() {
    const result: string[] = []

    if (this.startDate) {
      result.push(this.startDate.format(this.formatForDisplay))
    }

    if (this.endDate) {
      result.push(this.endDate.format(this.formatForDisplay))
    }

    return result
  }

  showValues() {
    let result = ''

    if (this.startDate) {
      result += this.startDate.format(this.formatForDisplay)
    }

    if (this.startDate && this.endDate) {
      result += ' - '
    }

    if (this.endDate) {
      result += this.endDate.format(this.formatForDisplay)
    }

    return result
  }

  serialize(): SerializedPeriodCalendarTableFilter {
    return {
      class: 'PeriodCalendarTableFilter',

      isVisible: this.isVisible,
      isSetOnlyFullDate: this.isSetOnlyFullDate,

      endDate: this.endDate?.toString(),
      startDate: this.startDate?.toString(),

      endDateParam: this.endDateParam,
      startDateParam: this.startDateParam,
      serializationType: this.serializationType,
      arkspaceSerializationTypeParam: this.arkspaceSerializationTypeParam,
    }
  }

  deserialize(data: SerializedPeriodCalendarTableFilter): PeriodCalendarTableFilter {
    super.deserialize(data)

    this.endDateParam = data.endDateParam
    this.startDateParam = data.startDateParam
    this.serializationType = data.serializationType
    this.arkspaceSerializationTypeParam = data.arkspaceSerializationTypeParam

    this.endDate = data.endDate ? moment(data.endDate) : undefined
    this.startDate = data.startDate ? moment(data.startDate) : undefined

    this.isVisible = data.isVisible
    this.isSetOnlyFullDate = data.isSetOnlyFullDate

    return this
  }

  getQueryParams(): { [code: string]: string | string[] } {
    const result: { [code: string]: string | string[] } = {}

    switch (this.serializationType) {
      case 'arkspace':
        if (!this.arkspaceSerializationTypeParam) throw new Error('Параметр arkspaceSerializationTypeParam не задан для PeriodCalendar')

        if (this.startDate || this.endDate) {
          result[this.arkspaceSerializationTypeParam] = [
            this.startDate ? this.startDate.format('YYYY-MM-DD') : 'none',
            this.endDate ? this.endDate.format('YYYY-MM-DD') : 'none',
          ]
        }

        break
      default:
        if (this.startDate) {
          result[this.startDateParam] = this.startDate.format('YYYY-MM-DD')
        }

        if (this.endDate) {
          result[this.endDateParam] = this.endDate.format('YYYY-MM-DD')
        }

        break
    }

    return result
  }
}

/**
 * * Relation select
 */

export class RelationSelectTableFilter extends TableFilter {
  endpoint!: string

  selectedParam!: string

  selectedValueKey!: string

  value: { id: string, name: string } | undefined = undefined

  defaultValue?: { id: string, name: string }

  fromColumn(c: ColumnInterface) {
    if (!isRelationSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать RelationSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.endpoint = c.header.endpoint || ''
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : undefined
    this.addValue(c.header.defaultValue)

    return this
  }

  addValue(value: FilterValue | undefined) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue)
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== undefined
  }

  getValues() {
    const result: string[] = []

    if (this.hasValues()) {
      result.push(this.value!.name)
    }

    return result
  }

  showValues() {
    const result: string[] = []

    if (this.hasValues()) {
      result.push(this.value!.name)
    }

    return String(result.length)
  }

  serialize(): SerializedRelationSelectTableFilter {
    return {
      class: 'RelationSelectTableFilter',
      value: this.value,
      endpoint: this.endpoint,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedRelationSelectTableFilter): RelationSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.endpoint = data.endpoint
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string } {
    const result: { [code: string]: string } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value![this.selectedValueKey as 'id']
    }

    return result
  }
}

export class MultipleRelationSelectTableFilter extends TableFilter {
  endpoint!: string

  selectedParam!: string

  selectedValueKey!: string

  value: Array<{ id: string, name: string }> = []

  defaultValue?: Array<{ id: string, name: string }>

  fromColumn(c: ColumnInterface) {
    if (!isMultipleRelationSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать MultipleRelationSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.endpoint = c.header.endpoint || ''
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : []
      this.addValue(c.header.defaultValue)
    }

    return this
  }

  addValue(value: Array<{ id: string, name: string }>) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue(value: string) {
    this.value = this.value.filter((item) => item.name !== value)
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue ?? [])
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return Boolean(this.value.length)
  }

  getValues() {
    return this.value.map((item) => item.name)
  }

  showValues() {
    return String(this.value.length)
  }

  serialize(): SerializedMultipleRelationSelectTableFilter {
    return {
      class: 'MultipleRelationSelectTableFilter',
      value: this.value,
      endpoint: this.endpoint,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedMultipleRelationSelectTableFilter): MultipleRelationSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.endpoint = data.endpoint
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string[] } {
    const result: { [code: string]: string[] } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value.map((item) => item[this.selectedValueKey as 'id'])
    }

    return result
  }
}

export class MultipleParamsRelationSelectTableFilter extends TableFilter {
  endpoint!: string

  selectedParamsMap!: { [key: string]: string }

  value: { [key: string]: string } | undefined = undefined

  titleKey!: string

  fromColumn(c: ColumnInterface) {
    if (!isMultipleParamsRelationSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать MultipleParamsRelationSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.endpoint = c.header.endpoint || ''
    this.selectedParamsMap = c.header.paramsMap
    this.titleKey = c.header.titleKey

    return this
  }

  addValue(value: FilterValue | undefined) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== undefined
  }

  getValues() {
    const result: string[] = []

    if (this.hasValues()) {
      return [this.value![this.titleKey]]
    }

    return result
  }

  showValues() {
    return String([this.value].length)
  }

  serialize(): SerializedMultipleParamsRelationSelectTableFilter {
    return {
      class: 'MultipleParamsRelationSelectTableFilter',
      value: this.value,
      endpoint: this.endpoint,
      selectedParamsMap: this.selectedParamsMap,
      isVisible: this.isVisible,
      titleKey: this.titleKey,
    }
  }

  deserialize(data: SerializedMultipleParamsRelationSelectTableFilter): MultipleParamsRelationSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.endpoint = data.endpoint
    this.selectedParamsMap = data.selectedParamsMap
    this.isVisible = data.isVisible
    this.titleKey = data.titleKey

    return this
  }

  getQueryParams(): { [code: string]: string } {
    const result: { [code: string]: string } = {}

    if (this.hasValues()) {
      Object.keys(this.selectedParamsMap).forEach((key) => {
        result[key] = this.value![this.selectedParamsMap[key]]
      })
    }

    return result
  }
}

/**
 * * Enum select
 */

export class EnumSelectTableFilter extends TableFilter {
  endpoint!: string

  selectedParam!: string

  selectedValueKey!: string

  value: { id: string, name: string } | undefined = undefined

  defaultValue?: { id: string, name: string }

  fromColumn(c: ColumnInterface) {
    if (!isEnumSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать EnumSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.endpoint = c.header.endpoint || ''
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : undefined
    this.addValue(c.header.defaultValue)

    return this
  }

  addValue(value: FilterValue | undefined) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue)
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== undefined
  }

  getValues() {
    const result: string[] = []

    if (this.hasValues()) {
      result.push(this.value!.name)
    }

    return result
  }

  showValues() {
    const result: string[] = []

    if (this.hasValues()) {
      result.push(this.value!.name)
    }

    return String(result.length)
  }

  serialize(): SerializedEnumSelectTableFilter {
    return {
      class: 'EnumSelectTableFilter',
      value: this.value,
      endpoint: this.endpoint,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedEnumSelectTableFilter): EnumSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.endpoint = data.endpoint
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string } {
    const result: { [code: string]: string } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value![this.selectedValueKey as 'id']
    }

    return result
  }
}

export class MultipleEnumSelectTableFilter extends TableFilter {
  endpoint!: string

  selectedParam!: string

  selectedValueKey!: string

  value: Array<{ id: string, name: string }> = []

  defaultValue?: Array<{ id: string, name: string }>

  fromColumn(c: ColumnInterface) {
    if (!isMultipleEnumSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать MultipleEnumSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.endpoint = c.header.endpoint || ''
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : []
      this.addValue(c.header.defaultValue)
    }

    return this
  }

  addValue(value: Array<{ id: string, name: string }>) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue(value: string) {
    this.value = this.value.filter((item) => item.name !== value)
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue ?? [])
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return Boolean(this.value.length)
  }

  getValues() {
    return this.value.map((item) => item.name)
  }

  showValues() {
    return String(this.value.length)
  }

  serialize(): SerializedMultipleEnumSelectTableFilter {
    return {
      class: 'MultipleEnumSelectTableFilter',
      value: this.value,
      endpoint: this.endpoint,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedMultipleEnumSelectTableFilter): MultipleEnumSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.endpoint = data.endpoint
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string[] } {
    const result: { [code: string]: string[] } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value.map((item) => item[this.selectedValueKey as 'id'])
    }

    return result
  }
}

/**
 * * Default select
 */

export class DefaultSelectTableFilter extends TableFilter {
  selectedParam!: string

  selectedValueKey!: string

  value: { id: string, name: string } | undefined = undefined

  defaultValue?: { id: string, name: string }

  fromColumn(c: ColumnInterface) {
    if (!isDefaultSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать DefaultSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : undefined
    this.addValue(c.header.defaultValue)

    return this
  }

  addValue(value: FilterValue | undefined) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue() {
    this.value = undefined
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue)
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return this.value !== undefined
  }

  getValues() {
    const result: string[] = []

    if (this.value) {
      result.push(this.value.name)
    }

    return result
  }

  showValues() {
    const result: string[] = []

    if (this.value) {
      result.push(this.value.name)
    }

    return String(result.length)
  }

  serialize(): SerializedDefaultSelectTableFilter {
    return {
      class: 'DefaultSelectTableFilter',
      value: this.value,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedDefaultSelectTableFilter): DefaultSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string } {
    const result: { [code: string]: string } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value![this.selectedValueKey as 'id']
    }

    return result
  }
}

export class MultipleDefaultSelectTableFilter extends TableFilter {
  selectedParam!: string

  selectedValueKey!: string

  value: Array<{ id: string, name: string }> = []

  defaultValue?: Array<{ id: string, name: string }>

  fromColumn(c: ColumnInterface) {
    if (!isMultipleDefaultSelectHeaderInterface(c.header)) {
      throw new Error(`Невозможно собрать MultipleDefaultSelectTableFilter из заголовка с типом ${c.header.type}`)
    }

    super.fromColumn(c)
    this.selectedParam = c.header.selectedParam
    this.selectedValueKey = c.header.selectedValueKey ?? 'id'
    if (c.header.defaultValue) {
      this.defaultValue = c.header.saveDefaultFilterValueOnReset ? c.header.defaultValue : []
      this.addValue(c.header.defaultValue)
    }

    return this
  }

  addValue(value: Array<{ id: string, name: string }>) {
    if (typeof value === 'string') {
      throw new Error(`Для фильтра невозможно установить значение ${typeof (value)}`)
    }

    this.value = value
    this.isVisible = this.hasValues()
  }

  removeValue(value: string) {
    this.value = this.value.filter((item) => item.name !== value)
    this.isVisible = this.hasValues()
  }

  removeValues(): void {
    this.addValue(this.defaultValue ?? [])
    this.isVisible = this.hasValues()
  }

  hasValues() {
    return Boolean(this.value.length)
  }

  getValues() {
    return this.value.map((item) => item.name)
  }

  showValues() {
    return String(this.value.length)
  }

  serialize(): SerializedMultipleDefaultSelectTableFilter {
    return {
      class: 'MultipleDefaultSelectTableFilter',
      value: this.value,
      selectedParam: this.selectedParam,
      selectedValueKey: this.selectedValueKey,
      isVisible: this.isVisible,
    }
  }

  deserialize(data: SerializedMultipleDefaultSelectTableFilter): MultipleDefaultSelectTableFilter {
    super.deserialize(data)

    this.value = data.value
    this.selectedParam = data.selectedParam
    this.selectedValueKey = data.selectedValueKey
    this.isVisible = data.isVisible

    return this
  }

  getQueryParams(): { [code: string]: string[] } {
    const result: { [code: string]: string[] } = {}

    if (this.hasValues()) {
      result[this.selectedParam] = this.value.map((item) => item[this.selectedValueKey as 'id'])
    }

    return result
  }
}

/**
 * * Fabrics
 */

export function filterFabric(column: ColumnInterface) {
  const { header } = column

  if (isTextSearchHeaderInterface(header)) {
    return (new TextTableFilter()).fromColumn(column)
  }

  if (isNumberSearchHeaderInterface(header)) {
    return (new NumberTableFilter()).fromColumn(column)
  }

  if (isRangeNumberHeaderInterface(header)) {
    return (new RangeNumberTableFilter()).fromColumn(column)
  }

  /**
   * * Calendars
   */

  if (isCalendarHeaderInterface(header)) {
    return (new CalendarTableFilter()).fromColumn(column)
  }

  if (isPeriodCalendarHeaderInterface(header)) {
    return (new PeriodCalendarTableFilter()).fromColumn(column)
  }

  /**
   * * Relation select
   */

  if (isRelationSelectHeaderInterface(header)) {
    return (new RelationSelectTableFilter()).fromColumn(column)
  }

  if (isMultipleRelationSelectHeaderInterface(header)) {
    return (new MultipleRelationSelectTableFilter()).fromColumn(column)
  }

  if (isMultipleParamsRelationSelectHeaderInterface(header)) {
    return (new MultipleParamsRelationSelectTableFilter()).fromColumn(column)
  }

  /**
   * * Enum select
   */

  if (isEnumSelectHeaderInterface(header)) {
    return (new EnumSelectTableFilter()).fromColumn(column)
  }

  if (isMultipleEnumSelectHeaderInterface(header)) {
    return (new MultipleEnumSelectTableFilter()).fromColumn(column)
  }

  /**
   * * Default select
   */

  if (isDefaultSelectHeaderInterface(header)) {
    return (new DefaultSelectTableFilter()).fromColumn(column)
  }

  if (isMultipleDefaultSelectHeaderInterface(header)) {
    return (new MultipleDefaultSelectTableFilter()).fromColumn(column)
  }

  return new TableFilter()
}

export function filterDeserializationFabric(column: SerializedTableFilter) {
  if (isSerializedTextTableFilter(column)) {
    return (new TextTableFilter()).deserialize(column)
  }

  if (isSerializedNumberTableFilter(column)) {
    return (new NumberTableFilter()).deserialize(column)
  }

  if (isSerializedRangeNumberTableFilter(column)) {
    return (new RangeNumberTableFilter()).deserialize(column)
  }

  /**
   * * Calendars
   */

  if (isSerializedCalendarTableFilter(column)) {
    return (new CalendarTableFilter()).deserialize(column)
  }

  if (isSerializedPeriodCalendarTableFilter(column)) {
    return (new PeriodCalendarTableFilter()).deserialize(column)
  }

  /**
   * * Relation select
   */

  if (isSerializedRelationSelectTableFilter(column)) {
    return (new RelationSelectTableFilter()).deserialize(column)
  }

  if (isSerializedMultipleRelationSelectTableFilter(column)) {
    return (new MultipleRelationSelectTableFilter()).deserialize(column)
  }

  if (isSerializedMultipleParamsRelationSelectTableFilter(column)) {
    return (new MultipleParamsRelationSelectTableFilter()).deserialize(column)
  }

  /**
   * * Enum select
   */

  if (isSerializedEnumSelectTableFilter(column)) {
    return (new EnumSelectTableFilter()).deserialize(column)
  }

  if (isSerializedMultipleEnumSelectTableFilter(column)) {
    return (new MultipleEnumSelectTableFilter()).deserialize(column)
  }

  /**
   * * Default select
   */

  if (isSerializedDefaultSelectTableFilter(column)) {
    return (new DefaultSelectTableFilter()).deserialize(column)
  }

  if (isSerializedMultipleDefaultSelectTableFilter(column)) {
    return (new MultipleDefaultSelectTableFilter()).deserialize(column)
  }

  return new TableFilter()
}
