import { useState, useEffect, useContext, useMemo, useRef } from 'react'
import { Flex, Checkbox, Spin, Typography } from 'antd'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../context/ApiServerContext'
import {
  getModelByName,
  getModelProperties
} from '../../../database/ObjectModels'
import ObjectProperty from './ObjectProperty'
import SimpleDateTimePropertyFilter from './SimpleDateTimePropertyFilter'
import { LoadingOutlined } from '@ant-design/icons'
const { Text } = Typography

const isDateTimeProperty = (property) =>
  property?.type === 'dateTime' || property?.type === 'date'

const getOptionKey = (option) => {
  if (option && typeof option === 'object') {
    if (option.objectType) {
      const { prefix } = getModelByName(option.objectType)
      if (option._reference != null) return `${prefix}:${option._reference}`
      if (option._id != null) return `${prefix}:${option._id}`
    }
    return String(option._id ?? option.type ?? JSON.stringify(option))
  }
  return String(option)
}

const getDisplayValue = (option, property) => {
  if (!property) return option
  if (property.type === 'object') {
    if (option && typeof option === 'object' && option._id) return option
    if (option != null) return { _id: option }
  }
  if (property.type === 'state') {
    if (option && typeof option === 'object' && option.type) return option
    if (option != null) return { type: option }
  }
  return option
}

const matchOptions = (options, selected) => {
  if (!selected?.length) return []
  const selectedKeys = new Set(selected.map(getOptionKey))
  return options.filter((option) => selectedKeys.has(getOptionKey(option)))
}

// Survive table reloads / popover remounts without refetching the same property.
const optionsCache = new Map()

const SimplePropertyFilter = ({
  modelType,
  propertyName,
  value = [],
  onChange,
  search = ''
}) => {
  const { getModelPropertyValues } = useContext(ApiServerContext)
  const getModelPropertyValuesRef = useRef(getModelPropertyValues)
  getModelPropertyValuesRef.current = getModelPropertyValues

  const cacheKey =
    modelType && propertyName ? `${modelType}:${propertyName}` : null
  const [options, setOptions] = useState(
    () => (cacheKey && optionsCache.get(cacheKey)) || []
  )
  const [loading, setLoading] = useState(
    () => !(cacheKey && optionsCache.has(cacheKey))
  )
  const [localChecked, setLocalChecked] = useState(null)

  const property = useMemo(
    () =>
      getModelProperties(modelType).find((prop) => prop.name === propertyName),
    [modelType, propertyName]
  )

  useEffect(() => {
    let cancelled = false

    const loadOptions = async () => {
      if (!modelType || !propertyName || isDateTimeProperty(property)) {
        setLoading(false)
        return
      }

      const key = `${modelType}:${propertyName}`
      const cached = optionsCache.get(key)
      if (cached) {
        setOptions(cached)
        setLoading(false)
        return
      }

      setLoading(true)
      try {
        const values = await getModelPropertyValuesRef.current(
          modelType,
          propertyName
        )
        if (cancelled) return
        const unique = [
          ...new Set((values || []).filter((v) => v != null && v !== ''))
        ]
        optionsCache.set(key, unique)
        setOptions(unique)
      } finally {
        if (!cancelled) setLoading(false)
      }
    }

    loadOptions()
    return () => {
      cancelled = true
    }
    // Intentionally omit getModelPropertyValues — context recreates it on every
    // provider render (e.g. table fetchLoading), which would refetch endlessly.
  }, [modelType, propertyName, property])

  const valueKey = useMemo(
    () => JSON.stringify((value || []).map(getOptionKey)),
    [value]
  )

  // Sync checkbox state from the applied filter (sidebar / column filteredValue).
  // No active filter => all options checked by default.
  useEffect(() => {
    if (options.length === 0) return
    if (value?.length > 0) {
      const matched = matchOptions(options, value)
      const matchedKeys = matched.map(getOptionKey)
      setLocalChecked(
        matchedKeys.length > 0 ? matchedKeys : value.map(getOptionKey)
      )
    } else {
      setLocalChecked(options.map(getOptionKey))
    }
    // valueKey captures value contents; value is read for matching
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [options, valueKey])

  const filteredOptions = useMemo(() => {
    const query = search.trim().toLowerCase()
    if (!query) return options
    return options.filter((option) =>
      getOptionKey(option).toLowerCase().includes(query)
    )
  }, [options, search])

  const optionKeys = useMemo(() => options.map(getOptionKey), [options])
  const checkedKeys = localChecked ?? optionKeys

  const emitChange = (nextKeys) => {
    setLocalChecked(nextKeys)
    // All selected (or empty options) means no filter applied
    if (optionKeys.length > 0 && nextKeys.length === optionKeys.length) {
      onChange?.([])
    } else {
      onChange?.(nextKeys)
    }
  }

  const handleChange = (visibleCheckedKeys) => {
    const filteredKeys = new Set(filteredOptions.map(getOptionKey))
    const hiddenSelected = checkedKeys.filter((key) => !filteredKeys.has(key))
    emitChange([...hiddenSelected, ...visibleCheckedKeys])
  }

  if (isDateTimeProperty(property)) {
    return (
      <SimpleDateTimePropertyFilter
        modelType={modelType}
        propertyName={propertyName}
        value={value}
        onChange={onChange}
        search={search}
      />
    )
  }

  return (
    <Spin spinning={loading} indicator={<LoadingOutlined spin />}>
      <Checkbox.Group
        value={checkedKeys}
        onChange={handleChange}
        style={{ width: '100%' }}
      >
        <Flex vertical gap={16} style={{ minWidth: 0 }}>
          {filteredOptions.map((option) => (
            <Flex gap={14} key={getOptionKey(option)} align='center'>
              <Checkbox
                value={getOptionKey(option)}
                style={{ minWidth: 0 }}
              ></Checkbox>
              <div style={{ minWidth: 0 }}>
                {property ? (
                  <ObjectProperty
                    {...property}
                    objectType={option?.objectType ?? property.objectType}
                    modelType={modelType}
                    value={getDisplayValue(option, property)}
                    inTable={true}
                    isEditing={false}
                    showLabel={false}
                  />
                ) : (
                  <Text>{String(option)}</Text>
                )}
              </div>
            </Flex>
          ))}
        </Flex>
      </Checkbox.Group>
    </Spin>
  )
}

SimplePropertyFilter.propTypes = {
  modelType: PropTypes.string,
  propertyName: PropTypes.string,
  value: PropTypes.array,
  onChange: PropTypes.func,
  search: PropTypes.string
}

export default SimplePropertyFilter
