import { DX_CLASSNAMES } from '@/constants/app.constant'
import { executeEditorScript } from '@/utils/editorScriptRuntime'
import {
  Form as FormDx,
  FormRef,
  GroupItem as GroupItemDx,
  SimpleItem as SimpleItemDx,
} from 'devextreme-react/form'
import { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form'
import { Dispatch, RefObject, useEffect, useRef, useState } from 'react'
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
import { RowMode, SimpleItemWithColData } from './types'
import { PlatformEditorTypes } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'

const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] =>
  items.flatMap((item) => [
    ...(item?.dataField ? [item] : []),
    ...flattenFormItems(item?.items || []),
    ...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])),
  ])

const updateReadOnlyInFormItems = (items: any[] = [], field: string, readOnly: boolean) => {
  let changed = false
  const expected = String(field || '').toLowerCase()

  const nextItems = items.map((item) => {
    const key = item?.dataField || item?.name
    let nextItem = item

    if (key && String(key).toLowerCase() === expected) {
      const editorOptions = nextItem.editorOptions || {}
      if (editorOptions.readOnly !== readOnly) {
        changed = true
        nextItem = {
          ...nextItem,
          editorOptions: {
            ...editorOptions,
            readOnly,
          },
        }
      }
    }

    if (nextItem?.items?.length) {
      const childResult = updateReadOnlyInFormItems(nextItem.items, field, readOnly)
      if (childResult.changed) {
        changed = true
        nextItem = { ...nextItem, items: childResult.items }
      }
    }

    if (nextItem?.tabs?.length) {
      const tabs = nextItem.tabs.map((tab: any) => {
        const tabResult = updateReadOnlyInFormItems(tab.items, field, readOnly)
        if (tabResult.changed) {
          changed = true
          return { ...tab, items: tabResult.items }
        }
        return tab
      })
      nextItem = tabs === nextItem.tabs ? nextItem : { ...nextItem, tabs }
    }

    return nextItem
  })

  return { items: nextItems, changed }
}

const findFormFieldKey = (items: any[] = [], field: string): string => {
  const expected = String(field || '').toLowerCase()

  for (const item of items || []) {
    const key = item?.dataField || item?.name
    if (key && String(key).toLowerCase() === expected) {
      return key
    }

    const childKey = findFormFieldKey(item?.items || [], field)
    if (childKey) {
      return childKey
    }

    for (const tab of item?.tabs || []) {
      const tabKey = findFormFieldKey(tab?.items || [], field)
      if (tabKey) {
        return tabKey
      }
    }
  }

  return field
}

const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
  if (!form?.option) return false

  const apply = () => {
    const formItems = form.option('items') || []
    const resolvedField = findFormFieldKey(formItems, field)
    const editor = form.getEditor?.(resolvedField) ?? form.getEditor?.(field)
    const result = updateReadOnlyInFormItems(formItems, resolvedField, readOnly)

    if (result.changed) {
      try {
        const item = form.itemOption?.(resolvedField) ?? form.itemOption?.(field)
        if (item) {
          form.itemOption?.(resolvedField, 'editorOptions', {
            ...(item.editorOptions || {}),
            readOnly,
          })
        } else {
          form.option('items', result.items)
        }
      } catch {
        form.option('items', result.items)
      }
    }

    const activeEditor = editor ?? form.getEditor?.(resolvedField) ?? form.getEditor?.(field)
    if (activeEditor?.option?.('readOnly') !== readOnly) {
      activeEditor?.option?.('readOnly', readOnly)
    }
  }

  apply()
  return true
}

const getValueByField = (data: Record<string, any> = {}, field?: string) => {
  if (!field) return undefined
  if (Object.prototype.hasOwnProperty.call(data, field)) return data[field]
  const key = Object.keys(data).find(
    (itemKey) => itemKey.toLowerCase() === String(field).toLowerCase(),
  )
  return key ? data[key] : undefined
}

const shouldRunEditorScriptOnContentReady = (script?: string) =>
  Boolean(
    script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')),
  )

const FormDevExpress = (props: {
  listFormCode: string
  isSubForm?: boolean
  mode: RowMode
  refForm: RefObject<FormRef>
  formData: any
  formItems: GroupItem[]
  setFormData: Dispatch<any>
}) => {
  const { listFormCode, isSubForm, mode, refForm, formData, formItems, setFormData } = props
  const { translate } = useLocalization()

  const formDataRef = useRef(formData)
  const formItemsRef = useRef(formItems)
  const formInstanceRef = useRef<any>()
  const lastContentReadyScriptKeyRef = useRef<string>()
  const didAutoFocusRef = useRef(false)
  const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({})
  const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({})

  const isTouchLikeDevice = () =>
    typeof window !== 'undefined' &&
    (window.matchMedia?.('(pointer: coarse)').matches ||
      window.matchMedia?.('(hover: none)').matches)

  useEffect(() => {
    formDataRef.current = formData
  }, [formData])

  useEffect(() => {
    formItemsRef.current = formItems
  }, [formItems])

  useEffect(() => {
    runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields
  }, [runtimeReadOnlyFields])

  useEffect(() => {
    didAutoFocusRef.current = false
  }, [listFormCode, mode])

  const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => {
    const resolvedField = findFormFieldKey(formItemsRef.current, field)
    const key = String(resolvedField || field || '').toLowerCase()
    if (!key || runtimeReadOnlyFieldsRef.current[key] === readOnly) {
      return
    }

    runtimeReadOnlyFieldsRef.current = {
      ...runtimeReadOnlyFieldsRef.current,
      [key]: readOnly,
    }
    setRuntimeReadOnlyFields(runtimeReadOnlyFieldsRef.current)
  }

  const getRuntimeEditorReadOnly = (formItem: SimpleItemWithColData) => {
    const field = formItem.dataField || formItem.name
    const resolvedField = findFormFieldKey(formItemsRef.current, field || '')
    const key = String(resolvedField || field || '').toLowerCase()
    return Object.prototype.hasOwnProperty.call(runtimeReadOnlyFields, key)
      ? runtimeReadOnlyFields[key]
      : undefined
  }

  const applyEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
    setRuntimeEditorReadOnly(field, readOnly)
    setFormEditorReadOnly(form, field, readOnly)
    setTimeout(() => setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly), 0)
  }

  const applyEditorScriptFormData = (form: any, newData: any) => {
    const nextFormData = {
      ...(formDataRef.current || {}),
      ...(newData || {}),
    }

    formDataRef.current = nextFormData
    form?.option?.('formData', nextFormData)

    Object.keys(newData || {}).forEach((field) => {
      form?.getEditor?.(field)?.option?.('value', newData[field])
    })

    setFormData(nextFormData)
  }

  const runEditorScript = (formItem: SimpleItemWithColData, eventValue: any, component?: any) => {
    if (!formItem?.editorScript) {
      return
    }

    const form = formInstanceRef.current ?? component
    const dataField = formItem.dataField
    const nextFormData = {
      ...(formDataRef.current || {}),
      ...(form?.option?.('formData') || {}),
      ...(dataField ? { [dataField]: eventValue } : {}),
    }
    formDataRef.current = nextFormData

    try {
      const editor = {
        dataField,
        component: form,
      }
      const formData = nextFormData
      const e = {
        component: form,
        dataField,
        value: eventValue,
      }
      const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
        applyEditorReadOnly(form, field, readOnly)

      executeEditorScript(formItem.editorScript, {
        formData,
        e,
        editor,
        runtimeSetEditorReadOnly,
        setFormData: (newData: any) => applyEditorScriptFormData(form, newData),
      })
    } catch (err) {
      console.error('Script execution failed for', formItem.name, err)
    }
  }

  const getEditorOptions = (formItem: SimpleItemWithColData, index?: number) => {
    const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
    const prevOnValueChanged = formItem.editorOptions?.onValueChanged

    return {
      ...(index !== undefined && mode !== 'view' && !isTouchLikeDevice()
        ? { autoFocus: index === 1 }
        : {}),
      ...(formItem.editorType === 'dxDateBox'
        ? {
            useMaskBehavior: true,
            openOnFieldClick: true,
            showClearButton: true,
          }
        : {}),
      ...(formItem.colData?.placeHolder
        ? { placeholder: translate('::' + formItem.colData.placeHolder) }
        : {}),
      ...formItem.editorOptions,
      ...(runtimeReadOnly !== undefined ? { readOnly: runtimeReadOnly } : {}),
      ...(mode === 'view' ? { readOnly: true } : {}),
      ...(formItem.editorScript
        ? {
            onValueChanged: (e: any) => {
              if (typeof prevOnValueChanged === 'function') {
                prevOnValueChanged(e)
              }
              if (formItem.dataField) {
                const nextFormData = {
                  ...(formDataRef.current || {}),
                  ...(formInstanceRef.current?.option?.('formData') || {}),
                  [formItem.dataField]: e?.value,
                }
                formDataRef.current = nextFormData
                formInstanceRef.current?.option?.('formData', nextFormData)
                setFormData(nextFormData)
              }
              runEditorScript(formItem, e?.value, formInstanceRef.current)
            },
          }
        : {}),
      buttons: (formItem.editorOptions?.buttons || []).map((btn: any) => {
        if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
          const origClick = eval(`(${btn.options.onClick})`)
          btn.options.onClick = (e: any) => {
            origClick({
              ...e,
              formData: formDataRef.current,
              fieldName: formItem.dataField,
              mode,
            })
          }
        }
        return btn
      }),
    }
  }

  const getFormItemKey = (formItem: SimpleItemWithColData, index: number) => {
    const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
    return `formItem-${formItem.dataField || formItem.name || index}-${String(runtimeReadOnly)}`
  }

  // formItems değiştiğinde (özellikle cascading alanlar için) editörlerin dataSource'larını güncelle
  useEffect(() => {
    if (!refForm.current?.instance()) return

    const allItems = formItems.flatMap((group) => flattenFormItems([group]))

    allItems.forEach((item) => {
      if (item.colData?.lookupDto?.dataSourceType && item.editorOptions?.dataSource) {
        try {
          const editor = refForm.current?.instance().getEditor(item.dataField!)
          if (editor) {
            editor.option('dataSource', item.editorOptions.dataSource)
          }
        } catch (err) {
          // Editor henüz oluşmamış olabilir, sessizce devam et
          console.debug('Editor update skipped for', item.dataField, err)
        }
      }
    })
  }, [formItems])

  // Cascade fieldlerin disabled durumunu güncelle
  const updateCascadeDisabledStates = () => {
    if (!refForm.current?.instance()) return

    const allItems = formItemsRef.current.flatMap((group) => flattenFormItems([group]))

    allItems.forEach((item) => {
      const cascadeParentFields = item.colData?.lookupDto?.cascadeParentFields
      if (cascadeParentFields) {
        const parentFields = cascadeParentFields.split(',').map((f: string) => f.trim())

        try {
          const editor = refForm.current?.instance().getEditor(item.dataField!)
          if (editor && mode !== 'view') {
            if (item.editorOptions?.disabled === true) {
              editor.option('disabled', true)
              return
            }

            // Parent fieldlerden en az biri boşsa disabled olmalı
            const shouldDisable = parentFields.some((parentField: string) => {
              return !formDataRef.current || !formDataRef.current[parentField]
            })

            editor.option('disabled', shouldDisable)
          }
        } catch (err) {
          console.debug('Cascade disabled update skipped for', item.dataField, err)
        }
      }
    })
  }

  // formData değiştiğinde cascade disabled durumlarını güncelle
  useEffect(() => {
    updateCascadeDisabledStates()
  }, [formData, mode])

  const runReadOnlyScripts = (form: any) => {
    if (!form) return

    const currentFormData = {
      ...(formDataRef.current || {}),
      ...(form?.option?.('formData') || {}),
    }
    formDataRef.current = currentFormData

    formItemsRef.current
      .flatMap((group) => flattenFormItems([group]))
      .filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
      .forEach((formItem) => {
        try {
          const editor = {
            dataField: formItem.dataField,
            component: form,
          }
          const formData = currentFormData
          const e = {
            component: form,
            dataField: formItem.dataField,
            value: getValueByField(currentFormData, formItem.dataField),
          }
          const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
            applyEditorReadOnly(form, field, readOnly)

          executeEditorScript(formItem.editorScript!, {
            formData,
            e,
            editor,
            runtimeSetEditorReadOnly,
            setFormData: (newData: any) => applyEditorScriptFormData(form, newData),
          })
        } catch (err) {
          console.error('Script execution failed on contentReady for', formItem.name, err)
        }
      })
  }

  useEffect(() => {
    const form = formInstanceRef.current
    if (!form || mode === 'view' || !formData || !formItems?.length) {
      return
    }

    const scriptFields = formItemsRef.current
      .flatMap((group) => flattenFormItems([group]))
      .filter((formItem) => shouldRunEditorScriptOnContentReady(formItem.editorScript))
      .map((formItem) => formItem.dataField)
      .join('|')

    if (!scriptFields) {
      return
    }

    const scriptKey = `${mode}|${scriptFields}|${JSON.stringify(formData)}`
    if (lastContentReadyScriptKeyRef.current === scriptKey) {
      return
    }

    lastContentReadyScriptKeyRef.current = scriptKey
    setTimeout(() => runReadOnlyScripts(form), 0)
  }, [formData, formItems, mode])

  return (
    <FormDx
      ref={refForm}
      className={`${DX_CLASSNAMES} ${!isSubForm ? 'px-2' : ''} pb-2`}
      formData={formData}
      onFieldDataChanged={async (e: FieldDataChangedEvent) => {
        if (!e.dataField) {
          return
        }

        const newFormData = { ...formData, [e.dataField]: e.value }
        let hasChanges = !Object.is(formData?.[e.dataField], e.value)

        // Cascading child field'leri temizle (parent field değiştiğinde)
        const allItems = formItemsRef.current.flatMap((group) => flattenFormItems([group]))
        const cascadingChildren = allItems.filter((item) => {
          const parentFields = item.colData?.lookupDto?.cascadeParentFields?.split(',') || []
          return parentFields.some((field) => field.trim() === e.dataField)
        })

        // Parent field değiştiğinde child field'leri temizle
        cascadingChildren.forEach((child) => {
          if (!Object.is(newFormData[child.dataField!], null)) {
            newFormData[child.dataField!] = null
            hasChanges = true
          }
        })

        if (hasChanges) {
          formDataRef.current = newFormData
          setFormData(newFormData)
        }

        // Cascade disabled durumlarını güncelle (setTimeout ile editor güncellemesinden sonra çalışsın)
        setTimeout(() => {
          updateCascadeDisabledStates()
        }, 0)
      }}
      onContentReady={(e) => {
        formInstanceRef.current = e.component

        const form = e.component

        runReadOnlyScripts(form)

        const groupItems = e.component.option('items') as any[]
        const firstItem = groupItems?.[0]?.items?.[0]

        if (!didAutoFocusRef.current && firstItem?.dataField && !isTouchLikeDevice()) {
          didAutoFocusRef.current = true
          const editor = e.component.getEditor(firstItem.dataField)
          mode !== 'view' && editor?.focus()
        }
      }}
    >
      {formItems.map((formGroupItem, i) => {
        return (
          <GroupItemDx
            key={'formGroupItem-' + i}
            colCount={formGroupItem.colCount}
            colSpan={formGroupItem.colSpan}
            caption={formGroupItem.caption}
          >
            {(formGroupItem.items as SimpleItemWithColData[])
              ?.filter((formItem) => {
                if (mode === 'edit') return formItem.allowEditing !== false
                if (mode === 'new') return formItem.allowAdding !== false
                return true
              })
              .map((formItem, i) => {
                return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? (
                  <SimpleItemDx
                    cssClass="font-semibold"
                    key={getFormItemKey(formItem, i)}
                    {...formItem}
                    render={() => (
                      <TagBoxEditorComponent
                        value={formData[formItem.dataField!] || []}
                        setDefaultValue={false}
                        values={formData}
                        options={formItem.tagBoxOptions}
                        col={formItem.colData}
                        onValueChanged={(e: any) => {
                          const newData = { ...formDataRef.current, [formItem.dataField!]: e }
                          formDataRef.current = newData
                          setFormData(newData)
                          runEditorScript(formItem, e, formInstanceRef.current)
                        }}
                        editorOptions={getEditorOptions(formItem)}
                      ></TagBoxEditorComponent>
                    )}
                    label={{
                      text: translate('::' + formItem.colData?.captionName),
                      className: 'font-semibold',
                    }}
                  ></SimpleItemDx>
                ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? (
                  <SimpleItemDx
                    cssClass="font-semibold"
                    key={getFormItemKey(formItem, i)}
                    {...formItem}
                    render={() => (
                      <GridBoxEditorComponent
                        value={formData[formItem.dataField!] || []}
                        values={formData}
                        options={formItem.gridBoxOptions}
                        col={formItem.colData}
                        onValueChanged={(e: any) => {
                          const newData = { ...formDataRef.current, [formItem.dataField!]: e }
                          formDataRef.current = newData
                          setFormData(newData)
                          runEditorScript(formItem, e, formInstanceRef.current)
                        }}
                        editorOptions={getEditorOptions(formItem)}
                      ></GridBoxEditorComponent>
                    )}
                    label={{
                      text: translate('::' + formItem.colData?.captionName),
                      className: 'font-semibold',
                    }}
                  ></SimpleItemDx>
                ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? (
                  <SimpleItemDx
                    cssClass="font-semibold"
                    key={getFormItemKey(formItem, i)}
                    dataField={formItem.dataField}
                    name={formItem.name}
                    colSpan={formItem.colSpan}
                    isRequired={formItem.isRequired}
                    render={() => (
                      <ImageUploadEditorComponent
                        value={formData[formItem.dataField!]}
                        options={formItem.imageUploadOptions}
                        onValueChanged={(val: any) => {
                          const newData = { ...formDataRef.current, [formItem.dataField!]: val }
                          formDataRef.current = newData
                          setFormData(newData)
                          runEditorScript(formItem, val, formInstanceRef.current)
                        }}
                        editorOptions={getEditorOptions(formItem)}
                      />
                    )}
                    label={{
                      text: translate('::' + formItem.colData?.captionName),
                      className: 'font-semibold',
                    }}
                  ></SimpleItemDx>
                ) : (
                  <SimpleItemDx
                    cssClass="font-semibold"
                    key={getFormItemKey(formItem, i)}
                    {...formItem}
                    editorOptions={getEditorOptions(formItem, i)}
                    label={{ text: translate('::' + formItem.colData?.captionName) }}
                  />
                )
              })}
          </GroupItemDx>
        )
      })}
    </FormDx>
  )
}

export default FormDevExpress
