import {
  useState,
  useEffect,
  useContext,
  useCallback,
  forwardRef,
  useImperativeHandle
} from 'react'
import { Form, message } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import PropTypes from 'prop-types'
import DeleteObjectModal from './DeleteObjectModal'
import merge from 'lodash/merge'

/**
 * ObjectForm is a reusable form component for editing any object type.
 * It handles fetching, updating, locking, unlocking, and validation logic.
 *
 * Props:
 * - id: string (required)
 * - type: string (required)
 * - formItems: array (for ObjectInfo/ObjectProperty items)
 * - children: function({
 *     loading, isEditing, startEditing, cancelEditing, handleUpdate, form, formValid, objectData, setIsEditing, setObjectData
 *   }) => ReactNode
 */
const ObjectForm = forwardRef(
  ({ id, type, style, children, onEdit, onStateChange }, ref) => {
    const [objectData, setObjectData] = useState(null)
    const [serverObjectData, setServerObjectData] = useState(null)
    const [fetchLoading, setFetchLoading] = useState(true)
    const [editLoading, setEditLoading] = useState(false)
    const [lock, setLock] = useState({})
    const [initialized, setInitialized] = useState(false)
    const [isEditing, setIsEditing] = useState(false)
    const [formValid, setFormValid] = useState(false)
    const [form] = Form.useForm()
    const formUpdateValues = Form.useWatch([], form)
    const [messageApi, contextHolder] = message.useMessage()
    const [deleteModalOpen, setDeleteModalOpen] = useState(false)
    const [deleteLoading, setDeleteLoading] = useState(false)
    const {
      fetchObject,
      updateObject,
      deleteObject,
      lockObject,
      unlockObject,
      fetchObjectLock,
      showError,
      connected,
      subscribeToObjectUpdates,
      subscribeToObjectLock
    } = useContext(ApiServerContext)
    const { token } = useContext(AuthContext)
    // Validate form on change
    useEffect(() => {
      form
        .validateFields({ validateOnly: true })
        .then(() => {
          setFormValid(true)
          onStateChange({ formValid: true, objectData: form.getFieldsValue() })
        })
        .catch(() => {
          onStateChange({ formValid: true, objectData: form.getFieldsValue() })
        })
    }, [form, formUpdateValues])

    // Cleanup on unmount
    useEffect(() => {
      return () => {
        if (id) {
          unlockObject(id, type)
        }
      }
    }, [id, type, unlockObject])

    const handleFetchObject = useCallback(async () => {
      try {
        setFetchLoading(true)
        onStateChange({ loading: true })
        const data = await fetchObject(id, type)
        const lockEvent = await fetchObjectLock(id, type)
        setLock(lockEvent)
        onStateChange({ lock: lockEvent })
        setObjectData(data)
        setServerObjectData(data)
        form.setFieldsValue(data)
        setFetchLoading(false)
        onStateChange({ loading: false })
      } catch (err) {
        console.error(err)
        messageApi.error('Failed to fetch object info')
        showError(
          `Failed to fetch object information. Message: ${err.message}. Code: ${err.code}`,
          fetchObject
        )
      }
    }, [fetchObject, fetchObjectLock, id, type, form, messageApi, showError])

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

    // Update event handler
    const updateLockEventHandler = useCallback((value) => {
      setLock((prev) => {
        onStateChange({ lock: { ...prev, ...value } })
        return { ...prev, ...value }
      })
    }, [])

    useEffect(() => {
      if (initialized == false && id && token != null) {
        setInitialized(true)
        handleFetchObject()
      }
    }, [id, initialized, handleFetchObject, token])

    useEffect(() => {
      if (id && connected) {
        const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
          id,
          type,
          updateObjectEventHandler
        )
        const lockUnsubscribe = subscribeToObjectLock(
          id,
          type,
          updateLockEventHandler
        )
        return () => {
          if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
          if (lockUnsubscribe) lockUnsubscribe()
        }
      }
    }, [
      id,
      type,
      subscribeToObjectUpdates,
      subscribeToObjectLock,
      updateObjectEventHandler,
      connected,
      updateLockEventHandler
    ])

    useEffect(() => {
      onStateChange({ objectData })
    }, [objectData])

    const startEditing = () => {
      setIsEditing(true)
      onStateChange({ isEditing: true })
      lockObject(id, type)
    }

    const cancelEditing = () => {
      if (serverObjectData) {
        form.setFieldsValue(serverObjectData)
        setObjectData(serverObjectData)
      }
      setIsEditing(false)
      onStateChange({ isEditing: false })
      unlockObject(id, type)
    }

    const handleUpdate = async () => {
      try {
        const value = await form.validateFields()
        setEditLoading(true)
        onStateChange({ editLoading: true })
        await updateObject(id, type, value)
        setObjectData({ ...objectData, ...value })
        setIsEditing(false)
        onStateChange({ isEditing: false })
        messageApi.success('Information updated successfully')
      } catch (err) {
        console.error(err)
        if (err.errorFields) {
          return
        }
        messageApi.error('Failed to update information')
        showError(
          `Failed to update information. Message: ${err.message}. Code: ${err.code}`,
          () => handleUpdate()
        )
      } finally {
        handleFetchObject()
        setEditLoading(false)
        onStateChange({ editLoading: false })
      }
    }

    const handleDelete = () => {
      setDeleteModalOpen(true)
    }

    const confirmDelete = async () => {
      setDeleteLoading(true)
      try {
        await deleteObject(id, type)
        setDeleteModalOpen(false)
        messageApi.success('Deleted successfully')
        // Optionally: trigger a callback to parent to remove this object from view
      } catch (err) {
        console.error(err)
        messageApi.error('Failed to delete')
        showError(
          `Failed to delete. Message: ${err.message}. Code: ${err.code}`,
          confirmDelete
        )
      } finally {
        setDeleteLoading(false)
      }
    }

    useImperativeHandle(ref, () => ({
      startEditing,
      cancelEditing,
      handleUpdate,
      handleDelete,
      confirmDelete,
      handleFetchObject,
      editLoading,
      fetchLoading,
      isEditing,
      objectData,
      lock
    }))

    return (
      <>
        <DeleteObjectModal
          open={deleteModalOpen}
          onOk={confirmDelete}
          onCancel={() => setDeleteModalOpen(false)}
          loading={deleteLoading}
          objectType={type}
          objectName={objectData?.name || objectData?.label || ''}
        />
        <Form
          form={form}
          layout='vertical'
          style={style}
          onValuesChange={(values) => {
            if (onEdit != undefined) {
              onEdit(values)
            }
            setObjectData((prev) => {
              return { ...prev, ...values }
            })
          }}
        >
          {contextHolder}
          {children({
            loading: fetchLoading,
            isEditing,
            startEditing,
            cancelEditing,
            handleUpdate,
            form,
            formValid,
            objectData,
            setIsEditing,
            setObjectData,
            editLoading,
            lock,
            handleFetchObject,
            handleDelete
          })}
        </Form>
      </>
    )
  }
)

ObjectForm.displayName = 'ObjectForm'

ObjectForm.propTypes = {
  id: PropTypes.string.isRequired,
  type: PropTypes.string.isRequired,
  children: PropTypes.func.isRequired,
  style: PropTypes.object,
  onEdit: PropTypes.func,
  onStateChange: PropTypes.func
}

export default ObjectForm
