import { useMemo, useEffect, useRef, useState, useCallback } from 'react'
import PropTypes from 'prop-types'
import { Table, Skeleton, Card, Button, Flex, Typography, Modal } from 'antd'
import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty'
import ObjectInfo from './ObjectInfo'
import { LoadingOutlined } from '@ant-design/icons'
import BinIcon from '../../Icons/BinIcon'
import EyeIcon from '../../Icons/EyeIcon'
import EditIcon from '../../Icons/EditIcon'
const { Text, Link, Title } = Typography

const DEFAULT_COLUMN_WIDTHS = {
  text: 200,
  number: 120,
  dateTime: 200,
  state: 200,
  id: 180,
  bool: 120,
  tags: 200
}

const getDefaultWidth = (type) => {
  return DEFAULT_COLUMN_WIDTHS[type] || 200
}

const resolveChangeValue = (val, type) => {
  if (type === 'bool') return val
  if (val?.target && typeof val.target === 'object') {
    return val.target.value
  }
  return val
}

const createSkeletonRows = (rowCount, keyPrefix, keyName) => {
  return Array.from({ length: rowCount }).map((_, index) => {
    const skeletonKey = `${keyPrefix}-${index}`
    const row = {
      isSkeleton: true,
      _objectChildTableKey: skeletonKey
    }

    if (typeof keyName === 'string') {
      row[keyName] = skeletonKey
    }

    return row
  })
}

const ObjectChildTable = ({
  maxWidth = '100%',
  name,
  properties = [],
  columns = [],
  visibleColumns = {},
  canAddRemove = true,
  objectData = null,
  scrollHeight = 240,
  size = 'small',
  loading = false,
  rowKey = '_id',
  skeletonRows = 5,
  additionalColumns = [],
  emptyText = 'No items',
  isEditing = false,
  value = [],
  rollups = [],
  onChange,
  hiddenPropertyWidth = '300px',
  minimal = false,
  label = '',
  ...tableProps
}) => {
  const mainTableWrapperRef = useRef(null)
  const rollupTableWrapperRef = useRef(null)
  const generatedRowKeysRef = useRef(new WeakMap())
  const generatedRowKeyCountRef = useRef(0)
  const [minimalModelOpen, setMinimalModelOpen] = useState(false)
  const [detailModalOpen, setDetailModalOpen] = useState(false)
  const [detailModalRecord, setDetailModalRecord] = useState(null)
  const [detailModalIndex, setDetailModalIndex] = useState(null)
  const getFallbackRowKey = (record) => {
    if (!record || typeof record !== 'object') {
      return `object-child-table-row-${String(record)}`
    }

    if (record._objectChildTableKey != null) {
      return record._objectChildTableKey
    }

    const existing = generatedRowKeysRef.current.get(record)
    if (existing) return existing

    const generated = `object-child-table-row-${generatedRowKeyCountRef.current}`
    generatedRowKeyCountRef.current += 1
    generatedRowKeysRef.current.set(record, generated)
    return generated
  }

  const getResolvedRecordKey = useCallback(
    (record) => {
      if (typeof rowKey === 'function') {
        return rowKey(record) ?? getFallbackRowKey(record)
      }

      if (typeof rowKey === 'string' && rowKey.length > 0) {
        return record?.[rowKey] ?? getFallbackRowKey(record)
      }

      return getFallbackRowKey(record)
    },
    [rowKey]
  )

  const propertyMap = useMemo(() => {
    const map = new Map()
    properties.forEach((property) => {
      if (property?.name) {
        map.set(property.name, property)
      }
    })
    return map
  }, [properties])

  const orderedPropertyNames = useMemo(() => {
    if (columns && columns.length > 0) {
      return columns
    }
    return properties.map((property) => property.name).filter(Boolean)
  }, [columns, properties])

  const resolvedProperties = useMemo(() => {
    const baseProperties =
      columns && columns.length > 0
        ? orderedPropertyNames
            .map((name) => propertyMap.get(name))
            .filter(Boolean)
        : properties

    return baseProperties.filter((property) => {
      if (!property?.name) return false
      if (
        visibleColumns &&
        Object.prototype.hasOwnProperty.call(visibleColumns, property.name)
      ) {
        return visibleColumns[property.name] !== false
      }
      return true
    })
  }, [columns, orderedPropertyNames, propertyMap, properties, visibleColumns])

  const hasHiddenProperties = useMemo(() => {
    return (
      Array.isArray(columns) &&
      columns.length > 0 &&
      Array.isArray(properties) &&
      columns.length < properties.length
    )
  }, [columns, properties])

  // When used inside antd Form.Item without Form.List, `value` will be the controlled array.
  const itemsSource = useMemo(() => {
    return value ?? []
  }, [value])

  const handleRemoveItem = useCallback(
    (record, index) => {
      const currentItems = Array.isArray(itemsSource) ? itemsSource : []

      let newItems
      if (typeof rowKey === 'string' && record[rowKey] != null) {
        newItems = currentItems.filter(
          (item) => item[rowKey] !== record[rowKey]
        )
      } else if (typeof rowKey === 'function') {
        const recordKey = getResolvedRecordKey(record)
        newItems = currentItems.filter((item) => {
          const itemKey = getResolvedRecordKey(item)
          return itemKey !== recordKey
        })
      } else {
        newItems = currentItems.filter((_, i) => i !== index)
      }

      if (typeof onChange === 'function') {
        onChange(newItems)
      }
    },
    [itemsSource, onChange, rowKey, getResolvedRecordKey]
  )

  const handleOpenDetailModal = useCallback((record, index) => {
    setDetailModalRecord({ ...record })
    setDetailModalIndex(index)
    setDetailModalOpen(true)
  }, [])

  const handleDetailPropertyChange = useCallback(
    (propName, newVal) => {
      const property = propertyMap.get(propName)
      const resolved = resolveChangeValue(newVal, property?.type)

      setDetailModalRecord((prev) =>
        prev ? { ...prev, [propName]: resolved } : prev
      )

      if (detailModalIndex == null) return

      const currentItems = Array.isArray(itemsSource) ? [...itemsSource] : []
      const existingRowKey =
        currentItems[detailModalIndex] != null
          ? getResolvedRecordKey(currentItems[detailModalIndex])
          : null
      const updatedItem = {
        ...currentItems[detailModalIndex],
        [propName]: resolved
      }

      if (existingRowKey) {
        generatedRowKeysRef.current.set(updatedItem, existingRowKey)
      }

      currentItems[detailModalIndex] = updatedItem
      if (typeof onChange === 'function') {
        onChange(currentItems)
      }
    },
    [propertyMap, detailModalIndex, itemsSource, onChange, getResolvedRecordKey]
  )

  const tableColumns = useMemo(() => {
    const propertyColumns = resolvedProperties.map((property) => ({
      title: property.label || property.name,
      dataIndex: property.name,
      key: property.name,
      width: property.columnWidth || getDefaultWidth(property.type),
      render: (_text, record, index) => {
        if (record?.isSkeleton) {
          return (
            <Skeleton.Input active size='small' style={{ width: '100%' }} />
          )
        }

        const handleCellChange = (newVal) => {
          const resolved = resolveChangeValue(newVal, property.type)
          const currentItems = Array.isArray(itemsSource)
            ? [...itemsSource]
            : []
          const existingRowKey = getResolvedRecordKey(record)
          const updatedItem = {
            ...currentItems[index],
            [property.name]: resolved
          }
          // Preserve fallback row identity across immutable updates so the row
          // is not remounted while typing (which causes input focus loss).
          generatedRowKeysRef.current.set(updatedItem, existingRowKey)
          currentItems[index] = updatedItem
          if (typeof onChange === 'function') {
            onChange(currentItems)
          }
        }

        return (
          <ObjectProperty
            {...property}
            longId={false}
            objectData={record}
            parentData={objectData}
            isEditing={isEditing}
            useFormItem={false}
            name={undefined}
            value={record[property.name]}
            onChange={handleCellChange}
          />
        )
      }
    }))

    const actionsColumn =
      hasHiddenProperties || (isEditing && canAddRemove)
        ? {
            title: '',
            key: 'actions',
            width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
            fixed: 'right',
            render: (_text, record, index) => {
              if (record?.isSkeleton) {
                return null
              }

              return (
                <Flex gap={4}>
                  {hasHiddenProperties && (
                    <Button
                      type='text'
                      size='small'
                      icon={isEditing ? <EditIcon /> : <EyeIcon />}
                      onClick={(e) => {
                        e.stopPropagation()
                        handleOpenDetailModal(record, index)
                      }}
                    />
                  )}
                  {isEditing && canAddRemove && (
                    <Button
                      type='text'
                      danger
                      size='small'
                      icon={<BinIcon />}
                      onClick={(e) => {
                        e.stopPropagation()
                        handleRemoveItem(record, index)
                      }}
                    />
                  )}
                </Flex>
              )
            }
          }
        : null

    return [
      ...propertyColumns,
      ...additionalColumns,
      ...(actionsColumn ? [actionsColumn] : [])
    ]
  }, [
    resolvedProperties,
    additionalColumns,
    hasHiddenProperties,
    isEditing,
    canAddRemove,
    itemsSource,
    onChange,
    getResolvedRecordKey,
    handleOpenDetailModal,
    handleRemoveItem,
    objectData
  ])

  const skeletonData = useMemo(() => {
    return createSkeletonRows(
      skeletonRows,
      'object-child-table-skeleton',
      typeof rowKey === 'string' ? rowKey : null
    )
  }, [skeletonRows, rowKey])

  const dataSource = useMemo(() => {
    if (loading && (!itemsSource || itemsSource.length === 0)) {
      return skeletonData
    }
    return itemsSource
  }, [itemsSource, loading, skeletonData])

  const resolvedRowKey = (record) => getResolvedRecordKey(record)

  const scrollConfig =
    scrollHeight != null
      ? { y: scrollHeight, x: 'max-content' }
      : { x: 'max-content' }

  const handleAddItem = () => {
    const newItem = {}

    resolvedProperties.forEach((property) => {
      if (
        property?.name &&
        !Object.prototype.hasOwnProperty.call(newItem, property.name)
      ) {
        newItem[property.name] = null
      }
    })

    const currentItems = Array.isArray(itemsSource) ? itemsSource : []
    const newItems = [...currentItems, newItem]

    if (typeof onChange === 'function') {
      onChange(newItems)
    }
  }

  const rollupDataSource = useMemo(() => {
    if (!rollups || rollups.length === 0) return []

    // Use value from form/props, or fall back to objectData when entering edit mode
    // (form may not have populated the field yet)
    const itemsForRollup =
      value ?? (name && objectData ? objectData[name] : null) ?? []

    // Build parent object with children array for rollup functions (e.g. objectData.parts)
    const updatedObjectData = { ...objectData }
    if (name) {
      updatedObjectData[name] = itemsForRollup
    }

    // Single summary row where each rollup value is placed under
    // the column that matches its `property` field.
    const summaryRow = {
      _objectChildTableKey: 'object-child-table-rollup-summary'
    }

    properties.forEach((property) => {
      const rollup = rollups.find(
        (r) => r.property && r.property === property.name
      )

      if (rollup && typeof rollup.value === 'function') {
        try {
          summaryRow[property.name] = rollup.value(updatedObjectData)
        } catch (e) {
          // Fail quietly but log for debugging
          console.error('Error computing rollup', rollup.name, e)
          summaryRow[property.name] = null
        }
      } else {
        summaryRow[property.name] = null
      }
    })

    return [summaryRow]
  }, [properties, rollups, objectData, value, name])

  const rollupColumns = useMemo(() => {
    const propertyColumns = resolvedProperties.map((property, index) => {
      const nextProperty = resolvedProperties[index + 1]
      var nextRollup = null
      if (nextProperty) {
        nextRollup = rollups?.find(
          (r) => r.property && r.property === nextProperty.name
        )
      }
      const rollupLabel = nextRollup?.label
      return {
        title: <Text>{property.label || property.name}</Text>,
        dataIndex: property.name,
        key: property.name,
        width: property.columnWidth || getDefaultWidth(property.type),
        render: (_text, record) => {
          return (
            <Flex justify={'space-between'}>
              <Text>
                {record[property.name] !== undefined &&
                  record[property.name] !== null && (
                    <>
                      {property?.prefix}
                      {record[property.name]}
                      {property?.suffix}
                    </>
                  )}
              </Text>
              {rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
            </Flex>
          )
        }
      }
    })
    const blankActionsColumn =
      hasHiddenProperties || (isEditing && canAddRemove)
        ? {
            title: '',
            key: 'actions',
            width: hasHiddenProperties && isEditing && canAddRemove ? 70 : 40,
            fixed: 'right',
            render: () => {
              return <Flex></Flex>
            }
          }
        : null
    return [
      ...propertyColumns,
      ...(blankActionsColumn ? [blankActionsColumn] : [])
    ]
  }, [
    resolvedProperties,
    rollups,
    isEditing,
    canAddRemove,
    hasHiddenProperties
  ])

  const hasRollups = useMemo(
    () => Array.isArray(rollups) && rollups.length > 0,
    [rollups]
  )

  useEffect(() => {
    if (!hasRollups || isEditing == null) return

    const mainWrapper = mainTableWrapperRef.current
    const rollupWrapper = rollupTableWrapperRef.current

    if (!mainWrapper || !rollupWrapper) return

    const mainBody =
      mainWrapper.querySelector('.ant-table-body') ||
      mainWrapper.querySelector('.ant-table-content')
    const rollupBody =
      rollupWrapper.querySelector('.ant-table-body') ||
      rollupWrapper.querySelector('.ant-table-content')

    if (!mainBody || !rollupBody) return

    let isSyncing = false

    const syncScroll = (source, target) => {
      if (!target) return
      isSyncing = true
      target.scrollLeft = source.scrollLeft
      window.requestAnimationFrame(() => {
        isSyncing = false
      })
    }

    const handleMainScroll = () => {
      if (isSyncing) return
      syncScroll(mainBody, rollupBody)
    }

    const handleRollupScroll = () => {
      if (isSyncing) return
      syncScroll(rollupBody, mainBody)
    }

    mainBody.addEventListener('scroll', handleMainScroll)
    rollupBody.addEventListener('scroll', handleRollupScroll)

    return () => {
      mainBody.removeEventListener('scroll', handleMainScroll)
      rollupBody.removeEventListener('scroll', handleRollupScroll)
    }
  }, [hasRollups, isEditing])

  const rollupTable = hasRollups ? (
    <div ref={rollupTableWrapperRef}>
      <Table
        dataSource={rollupDataSource}
        showHeader={false}
        columns={rollupColumns}
        loading={{ spinning: loading, indicator: <></> }}
        pagination={false}
        size={size}
        rowKey={resolvedRowKey}
        scroll={scrollConfig}
        locale={{ emptyText }}
        bordered={minimal}
        style={{ maxWidth: minimal ? '100%' : maxWidth, minWidth: 0 }}
        className='rollup-table'
      />
    </div>
  ) : null

  const tableComponent = (
    <Flex vertical>
      <div ref={mainTableWrapperRef}>
        <Table
          style={{ maxWidth: minimal ? '100%' : maxWidth, minWidth: 0 }}
          dataSource={dataSource}
          columns={tableColumns}
          loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
          size={size}
          rowKey={resolvedRowKey}
          scroll={scrollConfig}
          bordered={minimal}
          locale={{ emptyText }}
          pagination={false}
          className={hasRollups ? 'child-table-rollups' : 'child-table'}
          {...tableProps}
        />
      </div>
      {rollupTable}
    </Flex>
  )

  const detailModal = hasHiddenProperties ? (
    <Modal
      open={detailModalOpen}
      onCancel={() => setDetailModalOpen(false)}
      footer={null}
      width='900px'
    >
      {label ? (
        <Title
          level={2}
          style={{ marginTop: 0, lineHeight: '0.7', marginBottom: 20 }}
        >
          {label}
        </Title>
      ) : null}
      <ObjectInfo
        propertyDefinitions={properties}
        objectData={detailModalRecord}
        parentData={objectData}
        labelWidth={hiddenPropertyWidth}
        isEditing={isEditing}
        onPropertyChange={isEditing ? handleDetailPropertyChange : undefined}
        column={1}
        showLabels={true}
      />
    </Modal>
  ) : null

  if (isEditing === true) {
    return (
      <>
        <Card>
          <Flex vertical gap={'middle'}>
            <Flex justify={'space-between'}>
              <Button>Actions</Button>

              {canAddRemove && (
                <Button
                  type='primary'
                  icon={<PlusIcon />}
                  onClick={handleAddItem}
                />
              )}
            </Flex>

            {tableComponent}
          </Flex>
        </Card>
        {detailModal}
      </>
    )
  }

  if (minimal == true) {
    return (
      <>
        <Link
          onClick={() => {
            setMinimalModelOpen(true)
          }}
        >
          {value?.length || 0} {value?.length == 1 ? 'item' : 'items'}
        </Link>
        <Modal
          open={minimalModelOpen}
          onCancel={() => setMinimalModelOpen(false)}
          footer={null}
          width='860px'
        >
          <Title
            level={2}
            style={{ marginTop: 0, lineHeight: '0.7', marginBottom: 20 }}
          >
            {label}
          </Title>
          {tableComponent}
        </Modal>
        {detailModal}
      </>
    )
  }

  return (
    <>
      {tableComponent}
      {detailModal}
    </>
  )
}

ObjectChildTable.propTypes = {
  name: PropTypes.string,
  properties: PropTypes.arrayOf(
    PropTypes.shape({
      name: PropTypes.string.isRequired,
      label: PropTypes.string,
      type: PropTypes.string
    })
  ).isRequired,
  columns: PropTypes.arrayOf(PropTypes.string),
  visibleColumns: PropTypes.object,
  scrollHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  size: PropTypes.string,
  loading: PropTypes.bool,
  rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
  skeletonRows: PropTypes.number,
  additionalColumns: PropTypes.arrayOf(PropTypes.object),
  emptyText: PropTypes.node,
  label: PropTypes.string,
  isEditing: PropTypes.bool,
  value: PropTypes.arrayOf(PropTypes.object),
  onChange: PropTypes.func,
  maxWidth: PropTypes.string,
  rollups: PropTypes.arrayOf(PropTypes.object),
  objectData: PropTypes.object,
  canAddRemove: PropTypes.bool,
  minimal: PropTypes.bool,
  hiddenPropertyWidth: PropTypes.string
}

export default ObjectChildTable
