import PropTypes from 'prop-types'
import { Typography, Flex, Badge, Tag, Popover } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { useState, useEffect, useContext, useCallback, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { getModelByName } from '../../../database/ObjectModels'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import merge from 'lodash/merge'
import IdDisplay from './IdDisplay'
import SpotlightTooltip from './SpotlightTooltip'

const { Text, Link } = Typography

const ObjectDisplay = ({
  object,
  objectType,
  showHyperlink = false,
  showSpotlight = true
}) => {
  const [objectData, setObjectData] = useState(object)
  const [isHydrating, setIsHydrating] = useState(false)
  const { subscribeToObjectUpdates, connected, fetchSpotlightData } =
    useContext(ApiServerContext)
  const { token } = useContext(AuthContext)
  const navigate = useNavigate()
  const idRef = useRef(null)

  // Update event handler
  const updateObjectEventHandler = useCallback((value) => {
    setObjectData((prev) => merge({}, prev, value))
  }, [])

  // Ensure ID is valid before hydrate/subscribe (non-empty, not null/undefined)
  const isValidId = useCallback((id) => {
    return id != null && String(id).trim() !== ''
  }, [])

  // Extract string ID from object; handles both primitive _id and populated ref (_id as object)
  const getStringId = useCallback((obj) => {
    const id = obj?._id
    if (id == null) return null
    if (typeof id === 'string') return id
    if (typeof id === 'object' && id !== null && typeof id._id === 'string')
      return id._id
    return null
  }, [])

  // Detect minimal objects that only contain an _id (must be string, not populated object)
  const isMinimalObject = useCallback((obj) => {
    if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
    const keys = Object.keys(obj)
    const id = obj._id
    return (
      keys.length === 1 &&
      keys[0] === '_id' &&
      typeof id === 'string' &&
      isValidId(id)
    )
  }, [isValidId])

  // If only an _id is provided, fetch the full object via spotlight
  const fetchFullObjectIfNeeded = useCallback(
    async (obj) => {
      if (!isMinimalObject(obj) || !objectType) return obj
      try {
        const model = getModelByName(objectType)
        const spotlightQuery = `${model.prefix}:${obj._id}`
        const spotlightResult = await fetchSpotlightData(spotlightQuery)
        // Spotlight returns [] when not found; only use single-object results
        if (
          spotlightResult &&
          typeof spotlightResult === 'object' &&
          !Array.isArray(spotlightResult)
        ) {
          return spotlightResult
        }
      } catch (err) {
        console.error('Failed to fetch spotlight data:', err)
      }
      return obj
    },
    [fetchSpotlightData, isMinimalObject, objectType]
  )

  // Subscribe to object updates when component mounts
  useEffect(() => {
    const id = getStringId(object)
    if (isValidId(id) && objectType && connected && token != null) {
      const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
        id,
        objectType,
        updateObjectEventHandler
      )

      return () => {
        if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
      }
    }
  }, [
    object,
    objectType,
    subscribeToObjectUpdates,
    connected,
    token,
    updateObjectEventHandler,
    isValidId,
    getStringId
  ])

  // Update local state when object prop changes
  useEffect(() => {
    if (token == null) return
    const id = getStringId(object)
    if (!isValidId(id)) return
    const isMinimal = isMinimalObject(object)
    // Only skip re-fetch when we have a minimal object and already hydrated this id
    if (isMinimal && idRef.current === id) return
    let cancelled = false
    if (isMinimal) setIsHydrating(true)
    const hydrateObject = async () => {
      const fullObject = await fetchFullObjectIfNeeded(object)
      if (!cancelled) {
        setObjectData((prev) => merge({}, prev, fullObject))
        if (isMinimal) idRef.current = id
        setIsHydrating(false)
      }
    }
    hydrateObject()
    return () => {
      cancelled = true
      setIsHydrating(false)
    }
  }, [object, fetchFullObjectIfNeeded, isMinimalObject, isValidId, getStringId, token])
  if (!objectData) {
    return <Text type='secondary'>n/a</Text>
  }

  if (isHydrating) {
    return (
      <Tag
        style={{
          margin: 0,
          border: 'none',
          minWidth: 0,
          maxWidth: '100%'
        }}
        className='object-display-tag'
      >
        <Flex gap='4px' align='center' style={{ minWidth: 0, height: '24px' }}>
          <LoadingOutlined spin />
          <Text type='secondary'>Loading...</Text>
        </Flex>
      </Tag>
    )
  }

  const model = getModelByName(objectType)
  const Icon = model.icon
  const prefix = model.prefix

  // Get hyperlink URL from model's default actions
  var hyperlink = null
  const defaultModelActions =
    model.actions?.filter((action) => action.default == true) || []
  const objectId = getStringId(objectData)

  if (defaultModelActions.length >= 1 && objectId) {
    hyperlink = defaultModelActions[0].url(objectId)
  }

  // Render name with hyperlink/spotlight support
  const renderNameDisplay = () => {
    if (!objectData?.name) return null

    const textElement = (
      <Text ellipsis style={{ lineHeight: '1', paddingBottom: '2px' }}>
        {objectData.name}
      </Text>
    )

    // If hyperlink is enabled
    if (showHyperlink && hyperlink != null) {
      const linkElement = (
        <Link onClick={() => navigate(hyperlink)} ellipsis>
          {textElement}
        </Link>
      )

      if (showSpotlight && objectId) {
        return (
          <Popover
            content={
              <SpotlightTooltip
                query={prefix + ':' + objectId}
                type={objectType}
              />
            }
            trigger={['hover', 'click']}
            placement='topLeft'
            arrow={false}
            style={{ padding: 0 }}
          >
            {linkElement}
          </Popover>
        )
      }
      return linkElement
    }

    // If hyperlink is disabled
    if (showSpotlight && objectId) {
      return (
        <Popover
          content={
            <SpotlightTooltip
              query={prefix + ':' + objectId}
              type={objectType}
            />
          }
          trigger={['hover', 'click']}
          placement='topLeft'
          arrow={false}
        >
          {textElement}
        </Popover>
      )
    }
    return textElement
  }

  return (
    <Tag
      style={{
        margin: 0,
        border: 'none',
        minWidth: 0,
        maxWidth: '100%'
      }}
      className='object-display-tag'
    >
      <Flex
        gap={objectData?.color ? 'small' : '5px'}
        align='center'
        style={{ minWidth: 0, height: '24px' }}
      >
        <Icon />
        <Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
          {objectData?.color ? (
            <Badge
              color={objectData?.color}
              style={{ marginBottom: '1.5px' }}
            />
          ) : null}
          <div style={{ minWidth: 0 }}>
            {renderNameDisplay()}

            {objectId && !objectData?.name ? (
              <IdDisplay
                id={objectId}
                reference={objectData?._reference || undefined}
                type={objectType}
                longId={false}
                showCopy={false}
                showHyperlink={showHyperlink}
                showSpotlight={showSpotlight}
              />
            ) : null}
          </div>
        </Flex>
      </Flex>
    </Tag>
  )
}

ObjectDisplay.propTypes = {
  object: PropTypes.object,
  objectType: PropTypes.string,
  style: PropTypes.object,
  showHyperlink: PropTypes.bool,
  showSpotlight: PropTypes.bool
}

export default ObjectDisplay
