import { useMemo, useState, useEffect, useRef, useCallback } from 'react'
import { Card, Select, Button, Flex, Space, Dropdown } from 'antd'
import FilterInput from './FilterInput'
import { CloseOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
import PlusIcon from '../../Icons/PlusIcon'
import {
  getModelByName,
  getModelProperties
} from '../../../database/ObjectModels'
import MissingPlaceholder from './MissingPlaceholder'
import BinIcon from '../../Icons/BinIcon'

const hiddenSizerStyle = {
  gridArea: '1 / 1',
  visibility: 'hidden',
  whiteSpace: 'nowrap'
}

const FieldSelectLabel = ({ label, options }) => (
  <span style={{ display: 'inline-grid' }}>
    <span aria-hidden style={hiddenSizerStyle}>
      Field
    </span>
    {options.map((option) => (
      <span key={option.value} aria-hidden style={hiddenSizerStyle}>
        {option.label}
      </span>
    ))}
    <span style={{ gridArea: '1 / 1', whiteSpace: 'nowrap' }}>{label}</span>
  </span>
)

FieldSelectLabel.propTypes = {
  label: PropTypes.node,
  options: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.node,
      value: PropTypes.string
    })
  ).isRequired
}

const FilterSidebar = ({
  type,
  filter = {},
  onFilterChange,
  masterFilter = {}
}) => {
  const model = getModelByName(type)
  const modelProps = getModelProperties(type)
  const masterKeys = Object.keys(masterFilter)

  const initialEmptyFields = useRef(
    new Set(Object.keys(filter).filter((k) => filter[k] === ''))
  )
  const [localFilter, setLocalFilter] = useState(() => {
    const visible = {}
    for (const [k, v] of Object.entries(filter)) {
      if (v !== '') visible[k] = v
    }
    return visible
  })
  const debounceRef = useRef(null)

  useEffect(() => {
    const visible = {}
    for (const [k, v] of Object.entries(filter)) {
      if (initialEmptyFields.current.has(k) && v === '') continue
      visible[k] = v
    }
    setLocalFilter((prev) => {
      const prevKeys = Object.keys(prev)
      const nextKeys = Object.keys(visible)
      if (
        prevKeys.length === nextKeys.length &&
        nextKeys.every((key) => prev[key] === visible[key])
      ) {
        return prev
      }
      return visible
    })
  }, [filter])

  const debouncedFilterChange = useCallback(
    (next) => {
      if (debounceRef.current) clearTimeout(debounceRef.current)
      debounceRef.current = setTimeout(() => onFilterChange(next), 250)
    },
    [onFilterChange]
  )

  useEffect(() => {
    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current)
    }
  }, [])

  const filterableProperties = useMemo(() => {
    if (!model.filters) return []
    return model.filters
      .filter((name) => !masterKeys.includes(name))
      .map((name) => modelProps.find((p) => p.name === name))
      .filter(Boolean)
  }, [model.filters, modelProps, masterKeys])

  const fieldOptions = useMemo(
    () =>
      filterableProperties.map((prop) => ({
        label: prop.label,
        value: prop.name
      })),
    [filterableProperties]
  )

  const activeFields = Object.keys(localFilter)

  const usedFields = new Set(activeFields)
  const availableOptions = (currentField) =>
    fieldOptions.filter(
      (o) => !usedFields.has(o.value) || o.value === currentField
    )

  const rows =
    activeFields.length > 0
      ? activeFields.map((field) => ({ field, value: localFilter[field] }))
      : []

  const addRow = () => {
    const unused = fieldOptions.find((o) => !usedFields.has(o.value))
    if (!unused) return
    initialEmptyFields.current.delete(unused.value)
    const next = { ...localFilter, [unused.value]: '' }
    setLocalFilter(next)
  }

  const removeRow = (field) => {
    if (debounceRef.current) clearTimeout(debounceRef.current)
    const next = { ...localFilter }
    const hadValue = localFilter[field] !== ''
    delete next[field]
    setLocalFilter(next)
    if (hadValue) onFilterChange(next)
  }

  const changeField = (oldField, newField) => {
    if (debounceRef.current) clearTimeout(debounceRef.current)
    initialEmptyFields.current.delete(newField)
    const value = localFilter[oldField] || ''
    const next = { ...localFilter }
    delete next[oldField]
    next[newField] = value
    setLocalFilter(next)
    if (value !== '') onFilterChange(next)
  }

  const changeValue = (field, value) => {
    const next = { ...localFilter, [field]: value }
    setLocalFilter(next)
    debouncedFilterChange(next)
  }
  const menuItems = {
    items: [
      {
        key: 'clearAll',
        label: 'Clear all filters',
        danger: true,
        icon: <BinIcon />,
        onClick: () => clearAllFilters()
      }
    ]
  }

  const clearAllFilters = () => {
    setLocalFilter({})
    onFilterChange({})
  }

  return (
    <Card style={{ flex: 1, minHeight: 0, width: '100%', height: '100%' }}>
      <Flex vertical gap='middle'>
        <Flex justify='space-between'>
          <Dropdown menu={menuItems} trigger={['hover']} placement='bottomLeft'>
            <Button type='default'>Actions</Button>
          </Dropdown>
          <Button
            type='primary'
            icon={<PlusIcon />}
            onClick={addRow}
            disabled={usedFields.size >= fieldOptions.length}
            title='Add filter'
          />
        </Flex>
        <Flex vertical gap={8}>
          {rows.map((row) => (
            <Space.Compact key={row.field} style={{ width: '100%' }}>
              <Select
                placeholder='Field'
                value={row.field || undefined}
                onChange={(v) => changeField(row.field, v)}
                options={availableOptions(row.field)}
                allowClear={false}
                labelRender={({ label }) => (
                  <FieldSelectLabel label={label} options={fieldOptions} />
                )}
                style={{ flexShrink: 0 }}
                classNames={{ root: 'filter-sidebar-field-select' }}
              />
              <FilterInput
                placeholder='Value'
                value={row.value}
                onChange={(value) => changeValue(row.field, value)}
                style={{ flex: 1 }}
                propertyFilter={{
                  modelType: type,
                  propertyName: row.field
                }}
              />
              <Button
                icon={<CloseOutlined />}
                onClick={() => removeRow(row.field)}
                title='Remove filter'
              />
            </Space.Compact>
          ))}
          {rows.length === 0 && (
            <MissingPlaceholder message='No filters applied.' />
          )}
        </Flex>
      </Flex>
    </Card>
  )
}

FilterSidebar.propTypes = {
  type: PropTypes.string.isRequired,
  filter: PropTypes.object,
  onFilterChange: PropTypes.func.isRequired,
  masterFilter: PropTypes.object
}

export default FilterSidebar
