import type { Ref } from 'vue'
import { computed } from 'vue'

import type { DefaultOptionType, InternalFieldNames, ShowSearchType } from '../Cascader'

export const SEARCH_MARK = '__rc_cascader_search_mark__'

const defaultFilter: ShowSearchType['filter'] = (search, options, { label }) =>
  options.some((opt) => String(opt[label]).toLowerCase().includes(search.toLowerCase()))

const defaultRender: ShowSearchType['render'] = ({ path, fieldNames }) =>
  path.map((opt) => opt[fieldNames.label]).join(' / ')

export default (
  search: Ref<string>,
  options: Ref<DefaultOptionType[]>,
  fieldNames: Ref<InternalFieldNames>,
  prefixCls: Ref<string>,
  config: Ref<ShowSearchType>,
  changeOnSelect: Ref<boolean>,
) => {
  return computed(() => {
    const { filter = defaultFilter, render = defaultRender, limit = 50, sort } = config.value
    const filteredOptions: DefaultOptionType[] = []
    if (!search.value) {
      return []
    }

    function dig(list: DefaultOptionType[], pathOptions: DefaultOptionType[]) {
      list.forEach((option) => {
        // Perf saving when `sort` is disabled and `limit` is provided
        if (!sort && limit > 0 && filteredOptions.length >= limit) {
          return
        }

        const connectedPathOptions = [...pathOptions, option]
        const children = option[fieldNames.value.children]

        // If current option is filterable
        if (
          // If is leaf option
          !children ||
          children.length === 0 ||
          // If is changeOnSelect
          changeOnSelect.value
        ) {
          if (filter(search.value, connectedPathOptions, { label: fieldNames.value.label })) {
            filteredOptions.push({
              ...option,
              [fieldNames.value.label as 'label']: render({
                inputValue: search.value,
                path: connectedPathOptions,
                prefixCls: prefixCls.value,
                fieldNames: fieldNames.value,
              }),
              [SEARCH_MARK]: connectedPathOptions,
            })
          }
        }

        if (children) {
          dig(option[fieldNames.value.children] as DefaultOptionType[], connectedPathOptions)
        }
      })
    }

    dig(options.value, [])

    // Do sort
    if (sort) {
      filteredOptions.sort((a, b) => {
        return sort(a[SEARCH_MARK], b[SEARCH_MARK], search.value, fieldNames.value)
      })
    }

    return limit > 0 ? filteredOptions.slice(0, limit as number) : filteredOptions
  })
}
