import React, { useEffect, useState, useCallback } from 'react'
import PropTypes from 'prop-types'
import { TreeSelect, Typography, Flex, Badge, Space, Button, Input } from 'antd'
import axios from 'axios'
import { getTypeMeta } from '../utils/Utils'
import IdDisplay from './IdDisplay'
import CountryDisplay from './CountryDisplay'
import ReloadIcon from '../../Icons/ReloadIcon'
const { Text } = Typography
const { SHOW_CHILD } = TreeSelect
/**
 * ObjectSelect - a generic, reusable async TreeSelect for hierarchical object selection.
 *
 * Props:
 * - endpoint: API endpoint to fetch data from (required)
 * - propertyOrder: array of property names for category levels (required)
 * - filter: object for filtering (optional)
 * - useFilter: bool (optional)
 * - value: selected value (optional) - can be an object with _id, array of objects, or simple value/array
 * - onChange: function (optional)
 * - showSearch: bool (optional, default false)
 * - treeCheckable: bool (optional, default false) - enables multi-select mode with checkboxes
 * - treeSelectProps: any other TreeSelect props (optional)
 */
const ObjectSelect = ({
  endpoint,
  propertyOrder,
  filter = {},
  useFilter = false,
  value,
  onChange,
  showSearch = false,
  treeCheckable = false,
  treeSelectProps = {},
  type = 'unknown',
  ...rest
}) => {
  const [treeData, setTreeData] = useState([])
  const [loading, setLoading] = useState(false)
  const [defaultValue, setDefaultValue] = useState(treeCheckable ? [] : value)
  const [searchValue, setSearchValue] = useState('')
  const [error, setError] = useState(false)

  // Helper to get filter object for a node
  const getFilter = useCallback(
    (node) => {
      let filterObj = {}
      let currentId = node.id
      while (currentId !== 0) {
        const currentNode = treeData.find((d) => d.id === currentId)
        if (!currentNode) break
        filterObj[propertyOrder[currentNode.propertyId]] = currentNode.value
        currentId = currentNode.pId
      }
      return filterObj
    },
    [treeData, propertyOrder]
  )

  // Fetch data from API
  const fetchData = useCallback(
    async (property, filter, search) => {
      setLoading(true)
      setError(false)
      try {
        const params = { ...filter, property }
        if (search) params.search = search
        const response = await axios.get(endpoint, {
          params,
          withCredentials: true
        })
        setLoading(false)
        return response.data
      } catch (err) {
        setLoading(false)
        setError(true)
        // Optionally handle error
        return []
      }
    },
    [endpoint]
  )

  // Fetch single object by ID
  const fetchObjectById = useCallback(
    async (objectId) => {
      setLoading(true)
      setError(false)
      try {
        const response = await axios.get(`${endpoint}/${objectId}`, {
          withCredentials: true
        })
        setLoading(false)
        return response.data
      } catch (err) {
        setLoading(false)
        setError(true)
        console.error('Failed to fetch object by ID:', err)
        return null
      }
    },
    [endpoint]
  )

  // Helper to render the title for a node
  const renderTitle = useCallback(
    (item, isLeaf) => {
      if (!isLeaf) {
        // For category nodes, check if it's a country property
        const currentProperty = propertyOrder[item.propertyId]
        if (currentProperty === 'country' && item.value) {
          return <CountryDisplay countryCode={item.value} />
        }
        // For other category nodes, just show the value
        return <Text>{item[propertyOrder[item.propertyId]] || item.value}</Text>
      }
      // For leaf nodes, show icon, name, and id
      const meta = getTypeMeta(type)
      const Icon = meta.icon
      return (
        <Flex gap={'small'} align='center' style={{ width: '100%' }}>
          {Icon && <Icon />}
          {item?.color && <Badge color={item.color}></Badge>}
          <Text ellipsis>{item.name || type.title}</Text>
          <IdDisplay id={item._id} longId={false} type={type} />
        </Flex>
      )
    },
    [propertyOrder, type]
  )

  // Build tree path for a default object
  const buildTreePathForObject = useCallback(
    async (object) => {
      if (!object || !propertyOrder || propertyOrder.length === 0) return

      const newNodes = []
      let currentPId = 0

      // Build category nodes for each property level and load all available options
      for (let i = 0; i < propertyOrder.length; i++) {
        const propertyName = propertyOrder[i]
        let propertyValue

        // Handle nested property access (e.g., 'filament.diameter')
        if (propertyName.includes('.')) {
          const propertyPath = propertyName.split('.')
          let currentValue = object
          for (const prop of propertyPath) {
            if (currentValue && typeof currentValue === 'object') {
              currentValue = currentValue[prop]
            } else {
              currentValue = undefined
              break
            }
          }
          propertyValue = currentValue
        } else {
          propertyValue = object[propertyName]
        }

        // Build filter for this level
        let filterObj = {}
        for (let j = 0; j < i; j++) {
          const prevPropertyName = propertyOrder[j]
          let prevPropertyValue

          if (prevPropertyName.includes('.')) {
            const propertyPath = prevPropertyName.split('.')
            let currentValue = object
            for (const prop of propertyPath) {
              if (currentValue && typeof currentValue === 'object') {
                currentValue = currentValue[prop]
              } else {
                currentValue = undefined
                break
              }
            }
            prevPropertyValue = currentValue
          } else {
            prevPropertyValue = object[prevPropertyName]
          }

          if (prevPropertyValue !== undefined && prevPropertyValue !== null) {
            filterObj[prevPropertyName] = prevPropertyValue
          }
        }

        // Fetch all available options for this property level
        const data = await fetchData(propertyName, filterObj, '')

        // Create nodes for all available options at this level
        const levelNodes = data.map((item) => {
          let value
          if (typeof item === 'object' && item !== null) {
            if (propertyName.includes('.')) {
              const propertyPath = propertyName.split('.')
              let currentValue = item
              for (const prop of propertyPath) {
                if (currentValue && typeof currentValue === 'object') {
                  currentValue = currentValue[prop]
                } else {
                  currentValue = undefined
                  break
                }
              }
              value = currentValue
            } else {
              value = item[propertyName]
            }
          } else {
            value = item
          }

          return {
            id: value,
            pId: currentPId,
            value: value,
            key: value,
            propertyId: i,
            title: renderTitle({ ...item, value }, false),
            isLeaf: false,
            selectable: false,
            raw: item
          }
        })

        newNodes.push(...levelNodes)

        // Update currentPId to the object's property value for the next level
        if (propertyValue !== undefined && propertyValue !== null) {
          currentPId = propertyValue
        }
      }

      // Load all leaf nodes at the final level
      let finalFilterObj = {}
      for (let j = 0; j < propertyOrder.length - 1; j++) {
        const prevPropertyName = propertyOrder[j]
        let prevPropertyValue

        if (prevPropertyName.includes('.')) {
          const propertyPath = prevPropertyName.split('.')
          let currentValue = object
          for (const prop of propertyPath) {
            if (currentValue && typeof currentValue === 'object') {
              currentValue = currentValue[prop]
            } else {
              currentValue = undefined
              break
            }
          }
          prevPropertyValue = currentValue
        } else {
          prevPropertyValue = object[prevPropertyName]
        }

        if (prevPropertyValue !== undefined && prevPropertyValue !== null) {
          finalFilterObj[prevPropertyName] = prevPropertyValue
        }
      }

      const leafData = await fetchData(null, finalFilterObj, '')
      const leafNodes = leafData.map((item) => ({
        id: item._id || item.id || item.value,
        pId: currentPId,
        value: item._id || item.id || item.value,
        key: item._id || item.id || item.value,
        title: renderTitle(item, true),
        isLeaf: true,
        raw: item
      }))

      newNodes.push(...leafNodes)

      setTreeData(newNodes)
      setDefaultValue(object._id || object.id)
    },
    [propertyOrder, renderTitle, fetchData]
  )

  // Generate leaf nodes
  const generateLeafNodes = useCallback(
    async (node = null, filterArg = null, search = '') => {
      if (!node) return
      const actualFilter = filterArg === null ? getFilter(node) : filterArg
      const data = await fetchData(null, actualFilter, search)
      const newNodes = data.map((item) => {
        const isLeaf = true
        return {
          id: item._id || item.id || item.value,
          pId: node.id,
          value: item._id || item.id || item.value,
          key: item._id || item.id || item.value,
          title: renderTitle(item, isLeaf),
          isLeaf: true,
          raw: item
        }
      })
      setTreeData((prev) => [...prev, ...newNodes])
    },
    [fetchData, getFilter, renderTitle]
  )

  // Generate category nodes
  const generateCategoryNodes = useCallback(
    async (node = null, search = '') => {
      let filterObj = {}
      let propertyId = 0
      if (!node) {
        node = { id: 0 }
      } else {
        filterObj = getFilter(node)
        propertyId = node.propertyId + 1
      }
      const propertyName = propertyOrder[propertyId]
      const data = await fetchData(propertyName, filterObj, search)
      const newNodes = data.map((item) => {
        const isLeaf = false
        // Handle both cases: when item is a simple value or when it's an object
        let value
        if (typeof item === 'object' && item !== null) {
          // Handle nested property access (e.g., 'filament.diameter')
          if (propertyName.includes('.')) {
            const propertyPath = propertyName.split('.')
            let currentValue = item
            for (const prop of propertyPath) {
              if (currentValue && typeof currentValue === 'object') {
                currentValue = currentValue[prop]
              } else {
                currentValue = undefined
                break
              }
            }
            value = currentValue
          } else {
            // If item is an object, try to get the property value
            value = item[propertyName]
          }
        } else {
          // If item is a simple value (string, number, etc.), use it directly
          value = item
        }
        const title = renderTitle({ ...item, value }, isLeaf)
        return {
          id: value,
          pId: node.id,
          value: value,
          key: value,
          propertyId: propertyId,
          title: title,
          isLeaf: false,
          selectable: false,
          raw: item
        }
      })
      setTreeData((prev) => [...prev, ...newNodes])
    },
    [fetchData, getFilter, propertyOrder, renderTitle]
  )

  // Tree loader
  const handleTreeLoad = useCallback(
    async (node) => {
      if (node) {
        if (node.propertyId !== propertyOrder.length - 1) {
          await generateCategoryNodes(node, searchValue)
        } else {
          await generateLeafNodes(node, null, searchValue)
        }
      } else {
        await generateCategoryNodes(null, searchValue)
      }
    },
    [propertyOrder, generateCategoryNodes, generateLeafNodes, searchValue]
  )

  // OnChange handler
  const handleOnChange = (val, selectedOptions) => {
    if (onChange) {
      if (treeCheckable) {
        // Handle multiple selections with checkboxes
        const selectedObjects = []
        if (Array.isArray(val)) {
          val.forEach((selectedValue) => {
            const node = treeData.find((n) => n.value === selectedValue)
            if (node) {
              selectedObjects.push(node.raw)
            } else {
              selectedObjects.push(selectedValue)
            }
          })
        }
        onChange(selectedObjects, selectedOptions)
      } else {
        // Handle single selection
        const node = treeData.find((n) => n.value === val)
        onChange(node ? node.raw : val, selectedOptions)
      }
    }
    setDefaultValue(val)
  }

  // Search handler
  const handleSearch = (val) => {
    setSearchValue(val)
    setTreeData([])
  }

  // Keep defaultValue in sync and handle object values
  useEffect(() => {
    if (treeCheckable) {
      // Handle array of values for multi-select
      if (Array.isArray(value)) {
        const valueIds = value.map((v) => v._id || v.id || v)
        setDefaultValue(valueIds)

        // Load tree paths for any objects that aren't already loaded
        value.forEach((item) => {
          if (item && typeof item === 'object' && item._id) {
            const existingNode = treeData.find(
              (node) => node.value === item._id
            )
            if (!existingNode) {
              fetchObjectById(item._id).then((object) => {
                if (object) {
                  buildTreePathForObject(object)
                }
              })
            }
          }
        })
      } else {
        setDefaultValue([])
      }
    } else {
      // Handle single value
      if (value?._id) {
        setDefaultValue(value._id)
      }

      // Check if value is an object with _id (default object case)
      if (value && typeof value === 'object' && value._id) {
        // If we already have this object loaded, don't fetch again
        const existingNode = treeData.find((node) => node.value === value._id)
        if (!existingNode) {
          fetchObjectById(value._id).then((object) => {
            if (object) {
              buildTreePathForObject(object)
            }
          })
        }
      }
    }
  }, [value, treeData, fetchObjectById, buildTreePathForObject, treeCheckable])

  // Initial load
  useEffect(() => {
    if (treeData.length === 0 && !error && !loading) {
      // If we have a default object value, don't load the regular tree
      if (!treeCheckable && value && typeof value === 'object' && value._id) {
        return
      }

      if (useFilter || searchValue) {
        generateLeafNodes({ id: 0 }, filter, searchValue)
      } else {
        handleTreeLoad(null)
      }
    }
  }, [
    treeData,
    useFilter,
    filter,
    searchValue,
    generateLeafNodes,
    handleTreeLoad,
    error,
    loading,
    value,
    treeCheckable
  ])

  return error ? (
    <Space.Compact style={{ width: '100%' }}>
      <Input value='Failed to load data.' status='error' disabled />

      <Button
        icon={<ReloadIcon />}
        onClick={() => {
          setError(false)
          setTreeData([])
        }}
        danger
      />
    </Space.Compact>
  ) : (
    <TreeSelect
      treeDataSimpleMode
      treeDefaultExpandAll={true}
      loadData={handleTreeLoad}
      treeData={treeData}
      onChange={handleOnChange}
      loading={loading}
      value={loading ? 'Loading...' : defaultValue}
      showSearch={showSearch}
      onSearch={showSearch ? handleSearch : undefined}
      treeCheckable={treeCheckable}
      showCheckedStrategy={treeCheckable ? SHOW_CHILD : undefined}
      {...treeSelectProps}
      {...rest}
    />
  )
}

ObjectSelect.propTypes = {
  endpoint: PropTypes.string.isRequired,
  propertyOrder: PropTypes.arrayOf(PropTypes.string).isRequired,
  filter: PropTypes.object,
  useFilter: PropTypes.bool,
  value: PropTypes.any,
  onChange: PropTypes.func,
  showSearch: PropTypes.bool,
  treeCheckable: PropTypes.bool,
  treeSelectProps: PropTypes.object,
  type: PropTypes.string.isRequired
}

export default ObjectSelect
