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 { getModelByName } from '../../../database/ObjectModels'
import IdDisplay from './IdDisplay'
import ReloadIcon from '../../Icons/ReloadIcon'
import ObjectProperty from './ObjectProperty'
const { Text } = Typography
const { SHOW_CHILD } = TreeSelect

// --- Utility: Resolve nested property path (e.g., 'filament.diameter') ---
function resolvePropertyPath(obj, path) {
  if (!obj || !path) return { value: undefined, finalProperty: undefined }
  const props = path.split('.')
  let value = obj
  for (const prop of props) {
    if (value && typeof value === 'object') {
      value = value[prop]
    } else {
      return { value: undefined, finalProperty: prop }
    }
  }
  return { value, finalProperty: props[props.length - 1] }
}

// --- Utility: Build filter object for a node based on propertyOrder ---
function buildFilterForNode(node, treeData, propertyOrder) {
  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
}

/**
 * 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
}) => {
  // --- State ---
  const [treeData, setTreeData] = useState([])
  const [loading, setLoading] = useState(false)
  const [defaultValue, setDefaultValue] = useState(treeCheckable ? [] : value)
  const [searchValue, setSearchValue] = useState('')
  const [error, setError] = useState(false)

  // --- API: Fetch data for a property level or leaf ---
  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)
        return []
      }
    },
    [endpoint]
  )

  // --- API: Fetch a 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)
        return null
      }
    },
    [endpoint]
  )

  // --- Render node title ---
  const renderTitle = useCallback(
    (item) => {
      if (item.propertyType) {
        return (
          <ObjectProperty
            type={item.propertyType}
            value={item.value}
            objectType={type}
          />
        )
      } else {
        const model = getModelByName(type)
        const Icon = model.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>
        )
      }
    },
    [type]
  )

  // --- Build tree nodes for a property level ---
  const buildCategoryNodes = useCallback(
    (data, propertyName, propertyId, parentId) => {
      return data.map((item) => {
        let resolved = resolvePropertyPath(item, propertyName)
        let value = resolved.value
        let propertyType = resolved.finalProperty
        return {
          id: value,
          pId: parentId,
          value: value,
          key: value,
          propertyId: propertyId,
          title: renderTitle({ ...item, value, propertyType }),
          isLeaf: false,
          selectable: false,
          raw: item
        }
      })
    },
    [renderTitle]
  )

  // --- Build tree nodes for leaf level ---
  const buildLeafNodes = useCallback(
    (data, parentId) => {
      return data.map((item) => {
        const value = item._id || item.id || item.value
        return {
          id: value,
          pId: parentId,
          value: value,
          key: value,
          title: renderTitle(item),
          isLeaf: true,
          raw: item
        }
      })
    },
    [renderTitle]
  )

  // --- Tree loader: load children for a node or root ---
  const handleTreeLoad = useCallback(
    async (node) => {
      if (!propertyOrder.length) return
      if (node) {
        // Not at leaf level yet
        if (node.propertyId !== propertyOrder.length - 1) {
          const nextPropertyId = node.propertyId + 1
          const propertyName = propertyOrder[nextPropertyId]
          const filterObj = buildFilterForNode(node, treeData, propertyOrder)
          const data = await fetchData(propertyName, filterObj, searchValue)
          setTreeData((prev) => [
            ...prev,
            ...buildCategoryNodes(data, propertyName, nextPropertyId, node.id)
          ])
        } else {
          // At leaf level
          const filterObj = buildFilterForNode(node, treeData, propertyOrder)
          const data = await fetchData(null, filterObj, searchValue)
          setTreeData((prev) => [...prev, ...buildLeafNodes(data, node.id)])
        }
      } else {
        // Root load
        const propertyName = propertyOrder[0]
        const data = await fetchData(propertyName, {}, searchValue)
        setTreeData(buildCategoryNodes(data, propertyName, 0, 0))
      }
    },
    [
      propertyOrder,
      treeData,
      fetchData,
      buildCategoryNodes,
      buildLeafNodes,
      searchValue
    ]
  )

  // --- OnChange handler ---
  const handleOnChange = (val, selectedOptions) => {
    if (onChange) {
      if (treeCheckable) {
        // Multi-select
        let selectedObjects = []
        if (Array.isArray(val)) {
          selectedObjects = val.map((selectedValue) => {
            const node = treeData.find((n) => n.value === selectedValue)
            return node ? node.raw : selectedValue
          })
        }
        onChange(selectedObjects, selectedOptions)
      } else {
        // Single select
        const node = treeData.find((n) => n.value === val)
        onChange(node ? node.raw : val, selectedOptions)
      }
    }
    setDefaultValue(val)
  }

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

  // --- Sync defaultValue and load tree path for object values ---
  useEffect(() => {
    if (treeCheckable) {
      if (Array.isArray(value)) {
        const valueIds = value.map((v) => v._id || v.id || v)
        setDefaultValue(valueIds)
        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) {
                  // For multi-select, just add the leaf node
                  setTreeData((prev) => [
                    ...prev,
                    ...buildLeafNodes(
                      [object],
                      object[propertyOrder[propertyOrder.length - 2]] || 0
                    )
                  ])
                }
              })
            }
          }
        })
      } else {
        setDefaultValue([])
      }
    } else {
      if (value?._id) {
        setDefaultValue(value._id)
        const existingNode = treeData.find((node) => node.value === value._id)
        if (!existingNode) {
          fetchObjectById(value._id).then((object) => {
            if (object) {
              setTreeData((prev) => [
                ...prev,
                ...buildLeafNodes(
                  [object],
                  object[propertyOrder[propertyOrder.length - 2]] || 0
                )
              ])
            }
          })
        }
      }
    }
  }, [
    value,
    treeData,
    fetchObjectById,
    buildLeafNodes,
    propertyOrder,
    treeCheckable
  ])

  // --- Initial load ---
  useEffect(() => {
    if (treeData.length === 0 && !error && !loading) {
      if (!treeCheckable && value && typeof value === 'object' && value._id) {
        return
      }
      if (useFilter || searchValue) {
        // Flat filter mode
        fetchData(null, filter, searchValue).then((data) => {
          setTreeData(buildLeafNodes(data, 0))
        })
      } else {
        handleTreeLoad(null)
      }
    }
  }, [
    treeData,
    useFilter,
    filter,
    searchValue,
    buildLeafNodes,
    fetchData,
    handleTreeLoad,
    error,
    loading,
    value,
    treeCheckable
  ])

  // --- Error UI ---
  if (error) {
    return (
      <Space.Compact style={{ width: '100%' }}>
        <Input value='Failed to load data.' status='error' disabled />
        <Button
          icon={<ReloadIcon />}
          onClick={() => {
            setError(false)
            setTreeData([])
          }}
          danger
        />
      </Space.Compact>
    )
  }

  // --- Main TreeSelect UI ---
  return (
    <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
