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 { LoadingOutlined } from '@ant-design/icons'
import BinIcon from '../../Icons/BinIcon'
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,
  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 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 explicit = orderedPropertyNames
      .map((name) => propertyMap.get(name))
      .filter(Boolean)

    const remaining = properties.filter(
      (property) => !orderedPropertyNames.includes(property.name)
    )

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

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

  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}
            isEditing={isEditing}
            useFormItem={false}
            name={undefined}
            value={record[property.name]}
            onChange={handleCellChange}
          />
        )
      }
    }))

    const deleteColumn =
      isEditing && canAddRemove
        ? {
            title: '',
            key: 'delete',
            width: 10,
            fixed: 'right',
            render: (_text, record, index) => {
              if (record?.isSkeleton) {
                return null
              }
              return (
                <Button
                  type='text'
                  danger
                  size='small'
                  icon={<BinIcon />}
                  onClick={(e) => {
                    e.stopPropagation()
                    const currentItems = Array.isArray(itemsSource)
                      ? itemsSource
                      : []

                    // Use record's unique identifier if available, otherwise use index
                    let newItems
                    if (typeof rowKey === 'string' && record[rowKey] != null) {
                      // Use the unique key to find and remove the item
                      newItems = currentItems.filter(
                        (item) => item[rowKey] !== record[rowKey]
                      )
                    } else if (typeof rowKey === 'function') {
                      // If rowKey is a function, find the item by comparing resolved keys.
                      // Ant Design deprecates index-based rowKey callbacks.
                      const recordKey = getResolvedRecordKey(record)
                      newItems = currentItems.filter((item) => {
                        const itemKey = getResolvedRecordKey(item)
                        return itemKey !== recordKey
                      })
                    } else {
                      // Fallback to index-based removal
                      newItems = currentItems.filter((_, i) => i !== index)
                    }

                    if (typeof onChange === 'function') {
                      onChange(newItems)
                    }
                  }}
                />
              )
            }
          }
        : null

    return [
      ...propertyColumns,
      ...additionalColumns,
      ...(deleteColumn ? [deleteColumn] : [])
    ]
  }, [
    resolvedProperties,
    additionalColumns,
    isEditing,
    canAddRemove,
    itemsSource,
    onChange,
    rowKey,
    getResolvedRecordKey
  ])

  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]

    console.log('newItems', newItems)
    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 = properties.map((property, index) => {
      const nextProperty = properties[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 blankDeleteColumn =
      isEditing && canAddRemove
        ? {
            title: '',
            key: 'delete',
            width: 40,
            fixed: 'right',
            render: () => {
              return <Flex></Flex>
            }
          }
        : null
    return [
      ...propertyColumns,
      ...(blankDeleteColumn ? [blankDeleteColumn] : [])
    ]
  }, [properties, rollups, isEditing, canAddRemove])

  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: null }}
        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>
  )

  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>
    )
  }

  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>
      </>
    )
  }

  return tableComponent
}

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
}

export default ObjectChildTable
