import { useState, useEffect, useContext, useCallback, useRef } from 'react'
import { Form } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext'
import { useMessageContext } from '../context/MessageContext'
import PropTypes from 'prop-types'
import merge from 'lodash/merge'
import set from 'lodash/set'
import { getModelByName } from '../../../database/ObjectModels'

const buildObjectFromEntries = (entries = []) => {
  return entries.reduce((acc, entry) => {
    const { namePath, value } = entry || {}
    if (!Array.isArray(namePath) || value === undefined) {
      return acc
    }
    set(acc, namePath, value)
    return acc
  }, {})
}

/**
 * NewObjectForm is a reusable form component for creating new objects.
 * It handles form validation, submission, and error handling logic.
 *
 * Props:
 * - type: string (required)
 * - formItems: array (for ObjectInfo/ObjectProperty items)
 * - defaultValues: object (optional) - initial values for the form
 * - children: function({
 *     loading, isSubmitting, handleSubmit, form, formValid, objectData, setObjectData
 *   }) => ReactNode
 */
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
  const [objectData, setObjectData] = useState({
    ...defaultValues,
    _isEditing: true
  })
  const [submitLoading, setSubmitLoading] = useState(false)
  const [formValid, setFormValid] = useState(false)
  const [form] = Form.useForm()
  const validationRunRef = useRef(0)
  const formUpdateValues = Form.useWatch([], form)
  const { showSuccess, showError: showMessageError } = useMessageContext()
  const { createObject, showError } = useContext(ApiServerContext)

  const validateForm = useCallback(() => {
    const validationRun = ++validationRunRef.current
    let cancelled = false
    const timeoutId = setTimeout(() => {
      form
        .validateFields({ validateOnly: true })
        .then(() => {
          if (!cancelled && validationRun === validationRunRef.current) {
            setFormValid(true)
          }
        })
        .catch(() => {
          if (!cancelled && validationRun === validationRunRef.current) {
            setFormValid(false)
          }
        })
    }, 0)

    return () => {
      cancelled = true
      clearTimeout(timeoutId)
    }
  }, [form])

  // Get the model definition for this object type
  const model = getModelByName(type)

  // Function to calculate computed values from model properties
  const calculateComputedValues = useCallback(
    (currentData, modelDefinition) => {
      if (!modelDefinition || !Array.isArray(modelDefinition.properties)) {
        return []
      }

      const normalizedPath = (name, parentPath = []) => {
        if (Array.isArray(name)) {
          return [...parentPath, ...name]
        }
        if (typeof name === 'number') {
          return [...parentPath, name]
        }
        if (typeof name === 'string' && name.length > 0) {
          return [...parentPath, ...name.split('.')]
        }
        return parentPath
      }

      const getValueAtPath = (dataSource, path) => {
        if (!Array.isArray(path) || path.length === 0) {
          return dataSource
        }
        return path.reduce((acc, key) => {
          if (acc == null) return acc
          return acc[key]
        }, dataSource)
      }

      const computedEntries = []

      const processProperty = (property, scopeData, parentPath = []) => {
        if (!property?.name) return

        const propertyPath = normalizedPath(property.name, parentPath)

        if (property.value && typeof property.value === 'function') {
          try {
            const computedValue = property.value(scopeData || {})
            if (computedValue !== undefined) {
              computedEntries.push({
                namePath: propertyPath,
                value: computedValue
              })
            }
          } catch (error) {
            console.warn(
              `Error calculating value for property ${property.name}:`,
              error
            )
          }
        }

        if (
          Array.isArray(property.properties) &&
          property.properties.length > 0
        ) {
          if (property.type === 'objectChildren') {
            const childValues = getValueAtPath(currentData, propertyPath)
            if (Array.isArray(childValues)) {
              childValues.forEach((childData = {}, index) => {
                property.properties.forEach((childProperty) => {
                  processProperty(childProperty, childData || {}, [
                    ...propertyPath,
                    index
                  ])
                })
              })
            }
          } else {
            const nestedScope = getValueAtPath(currentData, propertyPath) || {}
            property.properties.forEach((childProperty) => {
              processProperty(childProperty, nestedScope || {}, propertyPath)
            })
          }
        }
      }

      modelDefinition.properties.forEach((property) => {
        processProperty(property, currentData)
      })

      return computedEntries
    },
    []
  )

  // Set initial form values when defaultValues change
  useEffect(() => {
    if (Object.keys(defaultValues).length > 0) {
      // Calculate computed values for initial data
      const computedEntries = calculateComputedValues(defaultValues, model)
      const computedValuesObject = buildObjectFromEntries(computedEntries)
      const initialFormData = merge({}, defaultValues, computedValuesObject)
      form.setFieldsValue(initialFormData)
      setObjectData((prev) => merge({}, prev, initialFormData))
      return validateForm()
    }
  }, [form, defaultValues, calculateComputedValues, model, validateForm])

  // Validate form on change
  useEffect(() => {
    return validateForm()
  }, [validateForm, formUpdateValues])

  const handleSubmit = async () => {
    try {
      setSubmitLoading(true)
      const computedEntries = calculateComputedValues(objectData, model)
      const computedValuesObject = buildObjectFromEntries(computedEntries)
      const computedObjectData = merge({}, objectData, computedValuesObject)
      const newObject = await createObject(type, computedObjectData)
      showSuccess('Object created successfully')
      return newObject
    } catch (err) {
      console.error(err)
      if (err.errorFields) {
        return
      }
      showMessageError('Failed to create object')
      showError(
        `Failed to create object. Message: ${err.message}. Code: ${err.code}`,
        () => handleSubmit()
      )
    } finally {
      setSubmitLoading(false)
    }
  }

  return (
    <Form
      form={form}
      layout='vertical'
      style={style}
      onValuesChange={(_changedValues, allFormValues) => {
        // Calculate computed values based on current form data
        const currentFormData = merge({}, objectData || {}, allFormValues)
        const computedEntries = calculateComputedValues(currentFormData, model)

        if (Array.isArray(computedEntries) && computedEntries.length > 0) {
          computedEntries.forEach(({ namePath, value }) => {
            if (!Array.isArray(namePath) || value === undefined) return
            const currentValue = form.getFieldValue(namePath)
            if (currentValue !== value) {
              if (typeof form.setFieldValue === 'function') {
                form.setFieldValue(namePath, value)
              } else {
                const fallbackPayload = buildObjectFromEntries([
                  { namePath, value }
                ])
                form.setFieldsValue(fallbackPayload)
              }
            }
          })
        }

        // Merge all values (user input + computed values)
        const computedValuesObject = buildObjectFromEntries(computedEntries)
        const allValues = merge({}, allFormValues, computedValuesObject)
        setObjectData((prev) => {
          return merge({}, prev, allValues)
        })
      }}
    >
      {children({
        submitLoading,
        handleSubmit,
        form,
        formValid,
        objectData,
        setObjectData
      })}
    </Form>
  )
}

NewObjectForm.propTypes = {
  type: PropTypes.string.isRequired,
  children: PropTypes.func.isRequired,
  style: PropTypes.object,
  defaultValues: PropTypes.object
}

export default NewObjectForm
