import type { Query as PiniaOrmQuery } from 'pinia-orm'
import type { Ref } from 'vue'
import type { ApiFilter } from '~/types/data'
import ApiResource from '~/models/ApiResource'
import { SEARCH_STRATEGY } from '~/types/enum/data'
import AbstractFilter from '~/services/data/Filters/AbstractFilter'
import RefUtils from '~/services/utils/refUtils'

export default class SearchFilter extends AbstractFilter implements ApiFilter {
  field: string
  filterValue: Ref<string | null>
  mode: SEARCH_STRATEGY

  /**
   * @param field
   * @param value
   * @param mode The 'search' strategy (exact [default], partial, start, end, word_start).
   *             This strategy is defined API-side, but PiniaOrm needs to know how to handle this.
   *             @see https://api-platform.com/docs/core/filters/
   * @param reactiveFilter
   */
  constructor(
    field: string,
    value: Ref<string | null>,
    mode: SEARCH_STRATEGY = SEARCH_STRATEGY.EXACT,
    reactiveFilter: boolean = false,
  ) {
    super(reactiveFilter)
    this.field = field
    this.filterValue = value
    this.mode = mode
  }

  protected search(value: string, filterValue: Ref<string | null>): boolean {
    if (filterValue.value === null) {
      return false
    }

    let wordStartRx: RegExp | null = null
    if (this.mode === SEARCH_STRATEGY.WORD_START) {
      wordStartRx = new RegExp(
        `^${filterValue.value}|\\s${filterValue.value}`,
        'i',
      )
    }

    if (this.mode === SEARCH_STRATEGY.EXACT) {
      return value === filterValue.value
    } else if (this.mode === SEARCH_STRATEGY.IEXACT) {
      return value.toLowerCase() === filterValue.value.toLowerCase()
    } else if (this.mode === SEARCH_STRATEGY.PARTIAL) {
      return value.includes(filterValue.value)
    } else if (this.mode === SEARCH_STRATEGY.IPARTIAL) {
      return value.toLowerCase().includes(filterValue.value.toLowerCase())
    } else if (this.mode === SEARCH_STRATEGY.START) {
      return value.startsWith(filterValue.value)
    } else if (this.mode === SEARCH_STRATEGY.END) {
      return value.endsWith(filterValue.value)
    } else if (this.mode === SEARCH_STRATEGY.WORD_START) {
      return wordStartRx!.test(value)
    } else {
      throw new Error('Unrecognized mode')
    }
  }

  public applyToPiniaOrmQuery(
    query: PiniaOrmQuery<ApiResource>,
  ): PiniaOrmQuery<ApiResource> {
    const filterValue = RefUtils.castToRef(
      this.filterValue,
      this.reactiveFilter,
    )

    if (!filterValue.value) {
      return query
    }

    return query.where(this.field, (value: string) =>
      this.search(value, filterValue),
    )
  }

  public getApiQueryPart(): string {
    if (!this.filterValue.value) {
      return ''
    }

    return `${this.field}[]=${this.filterValue.value}`
  }
}
