import type {
  SearchContext,
  Filter,
  PaginationState,
  MeiliSearchMultiSearchParams,
  Mutable,
} from '../../types/index.js'

import { adaptGeoSearch } from './geo-rules-adapter.js'
import { adaptFilters } from './filter-adapter.js'

function isPaginationRequired(
  filter: Filter,
  query?: string,
  placeholderSearch?: boolean
): boolean {
  // To disable pagination:
  // placeholderSearch must be disabled
  // The search query must be empty
  // There must be no filters
  return !(!placeholderSearch && !query && (!filter || filter.length === 0))
}

function setScrollPagination(
  pagination: PaginationState,
  paginationRequired: boolean
): { limit: number; offset: number } {
  const { page, hitsPerPage } = pagination

  if (!paginationRequired) {
    return {
      limit: 0,
      offset: 0,
    }
  }

  return {
    limit: hitsPerPage + 1,
    offset: page * hitsPerPage,
  }
}

function setFinitePagination(
  pagination: PaginationState,
  paginationRequired: boolean
): { hitsPerPage: number; page: number } {
  const { page, hitsPerPage } = pagination

  if (!paginationRequired) {
    return {
      hitsPerPage: 0,
      page: page + 1,
    }
  } else {
    return {
      hitsPerPage,
      page: page + 1,
    }
  }
}
/**
 * Adapts instantsearch.js and instant-meilisearch options to meilisearch search
 * query parameters.
 *
 * @param {SearchContext} searchContext
 */
export function MeiliParamsCreator(searchContext: SearchContext) {
  const {
    query,
    indexUid,
    facets,
    attributesToSnippet,
    snippetEllipsisText,
    filters,
    numericFilters,
    facetFilters,
    attributesToRetrieve,
    attributesToHighlight,
    highlightPreTag,
    highlightPostTag,
    placeholderSearch,
    pagination,
    sort,
    restrictSearchableAttributes,
    meiliSearchParams: overrideParams,
  } = searchContext
  const meiliSearchParams: MeiliSearchMultiSearchParams = { indexUid }

  const meilisearchFilters = adaptFilters(filters, numericFilters, facetFilters)

  return {
    getParams(): MeiliSearchMultiSearchParams {
      return meiliSearchParams
    },
    addQuery() {
      meiliSearchParams.q = query
    },
    addFacets() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.facets ??
        overrideParams?.facets ??
        <Mutable<typeof facets>>facets
      if (value !== undefined) {
        // despite Instantsearch.js typing it as `string[]`,
        // it still can send `string`
        meiliSearchParams.facets = typeof value === 'string' ? [value] : value
      }
    },
    addAttributesToCrop() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.attributesToCrop ??
        overrideParams?.attributesToCrop ??
        <Mutable<typeof attributesToSnippet>>attributesToSnippet
      if (value !== undefined) {
        meiliSearchParams.attributesToCrop = value
      }
    },
    addCropLength() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.cropLength ??
        overrideParams?.cropLength
      if (value !== undefined) {
        meiliSearchParams.cropLength = value
      }
    },
    addCropMarker() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.cropMarker ??
        overrideParams?.cropMarker ??
        snippetEllipsisText
      if (value !== undefined) {
        meiliSearchParams.cropMarker = value
      }
    },
    addFilters() {
      const overrideFilter =
        overrideParams?.indexesOverrides?.[indexUid]?.filter ??
        overrideParams?.filter

      if (overrideFilter !== undefined) {
        meiliSearchParams.filter = overrideFilter
      } else if (meilisearchFilters.length) {
        meiliSearchParams.filter = meilisearchFilters
      }
    },
    addAttributesToRetrieve() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.attributesToRetrieve ??
        overrideParams?.attributesToRetrieve ??
        <Mutable<typeof attributesToRetrieve>>attributesToRetrieve
      if (value !== undefined) {
        meiliSearchParams.attributesToRetrieve = value
      }
    },
    addAttributesToHighlight() {
      meiliSearchParams.attributesToHighlight = overrideParams
        ?.indexesOverrides?.[indexUid]?.attributesToHighlight ??
        overrideParams?.attributesToHighlight ??
        <Mutable<typeof attributesToHighlight>>attributesToHighlight ?? ['*']
    },
    addPreTag() {
      meiliSearchParams.highlightPreTag =
        overrideParams?.indexesOverrides?.[indexUid]?.highlightPreTag ??
        overrideParams?.highlightPreTag ??
        highlightPreTag ??
        '__ais-highlight__'
    },
    addPostTag() {
      meiliSearchParams.highlightPostTag =
        overrideParams?.indexesOverrides?.[indexUid]?.highlightPostTag ??
        overrideParams?.highlightPostTag ??
        highlightPostTag ??
        '__/ais-highlight__'
    },
    addPagination() {
      const paginationRequired = isPaginationRequired(
        meilisearchFilters,
        query,
        placeholderSearch
      )
      if (pagination.finite) {
        const { hitsPerPage, page } = setFinitePagination(
          pagination,
          paginationRequired
        )
        meiliSearchParams.hitsPerPage = hitsPerPage
        meiliSearchParams.page = page
      } else {
        const { limit, offset } = setScrollPagination(
          pagination,
          paginationRequired
        )
        meiliSearchParams.limit = limit
        meiliSearchParams.offset = offset
      }
    },
    addSort() {
      const overrideSort =
        overrideParams?.indexesOverrides?.[indexUid]?.sort ??
        overrideParams?.sort

      const value = overrideSort ?? sort
      if (value && (Array.isArray(value) ? value.length : true)) {
        meiliSearchParams.sort = Array.isArray(value) ? value : [value]
      }
    },
    addGeoSearchFilter() {
      const {
        insidePolygon,
        insideBoundingBox,
        aroundLatLng,
        aroundRadius,
        minimumAroundRadius,
      } = searchContext

      const filter = adaptGeoSearch({
        insidePolygon,
        insideBoundingBox,
        aroundLatLng,
        aroundRadius,
        minimumAroundRadius,
      })

      if (filter !== undefined) {
        if (Array.isArray(meiliSearchParams.filter)) {
          meiliSearchParams.filter.unshift(filter)
        } else {
          meiliSearchParams.filter = [filter]
        }
      }
    },
    addShowMatchesPosition() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.showMatchesPosition ??
        overrideParams?.showMatchesPosition
      if (value !== undefined) {
        meiliSearchParams.showMatchesPosition = value
      }
    },
    addMatchingStrategy() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.matchingStrategy ??
        overrideParams?.matchingStrategy
      if (value !== undefined) {
        meiliSearchParams.matchingStrategy = value
      }
    },
    addShowRankingScore() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.showRankingScore ??
        overrideParams?.showRankingScore
      if (value !== undefined) {
        meiliSearchParams.showRankingScore = value
      }
    },
    addAttributesToSearchOn() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.attributesToSearchOn ??
        overrideParams?.attributesToSearchOn ??
        <Mutable<typeof restrictSearchableAttributes>>(
          restrictSearchableAttributes
        )

      if (value !== undefined) {
        meiliSearchParams.attributesToSearchOn = value
      }
    },
    addHybridSearch() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.hybrid ??
        overrideParams?.hybrid
      if (value !== undefined) {
        meiliSearchParams.hybrid = value
      }
    },
    addVector() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.vector ??
        overrideParams?.vector
      if (value !== undefined) {
        meiliSearchParams.vector = value
      }
    },
    addDistinct() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.distinct ??
        overrideParams?.distinct
      if (value !== undefined) {
        meiliSearchParams.distinct = value
      }
    },
    addRankingScoreThreshold() {
      const value =
        overrideParams?.indexesOverrides?.[indexUid]?.rankingScoreThreshold ??
        overrideParams?.rankingScoreThreshold
      if (value !== undefined) {
        meiliSearchParams.rankingScoreThreshold = value
      }
    },
  }
}

/**
 * Adapt search request from instantsearch.js to search request compliant with
 * Meilisearch
 *
 * @param {SearchContext} searchContext
 * @returns {MeiliSearchMultiSearchParams}
 */
export function adaptSearchParams(
  searchContext: SearchContext
): MeiliSearchMultiSearchParams {
  const meilisearchParams = MeiliParamsCreator(searchContext)
  meilisearchParams.addQuery()
  meilisearchParams.addFacets()
  meilisearchParams.addAttributesToCrop()
  meilisearchParams.addCropLength()
  meilisearchParams.addCropMarker()
  meilisearchParams.addFilters()
  meilisearchParams.addAttributesToRetrieve()
  meilisearchParams.addAttributesToHighlight()
  meilisearchParams.addPreTag()
  meilisearchParams.addPostTag()
  meilisearchParams.addPagination()
  meilisearchParams.addSort()
  meilisearchParams.addGeoSearchFilter()
  meilisearchParams.addShowMatchesPosition()
  meilisearchParams.addMatchingStrategy()
  meilisearchParams.addShowRankingScore()
  meilisearchParams.addAttributesToSearchOn()
  meilisearchParams.addHybridSearch()
  meilisearchParams.addVector()
  meilisearchParams.addDistinct()
  meilisearchParams.addRankingScoreThreshold()

  return meilisearchParams.getParams()
}
