import { useMemo, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import { Table, Skeleton, Card, Button, Flex, Form, Typography } from 'antd'
import PlusIcon from '../../Icons/PlusIcon'
import ObjectProperty from './ObjectProperty'
import { LoadingOutlined } from '@ant-design/icons'
const { Text } = 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 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%',
  properties = [],
  columns = [],
  visibleColumns = {},
  objectData = null,
  scrollHeight = 240,
  size = 'small',
  loading = false,
  rowKey = '_id',
  skeletonRows = 5,
  additionalColumns = [],
  emptyText = 'No items',
  isEditing = false,
  formListName,
  value = [],
  rollups = [],
  onChange,
  ...tableProps
}) => {
  const mainTableWrapperRef = useRef(null)
  const rollupTableWrapperRef = useRef(null)

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

  // When used with antd Form.List, grab the form instance so we can read
  // the latest row values and pass them into ObjectProperty as objectData.
  // Assumes this component is rendered within a Form context when editing.
  const formInstance = Form.useFormInstance()

  const listNamePath = useMemo(() => {
    if (!formListName) return null
    return Array.isArray(formListName) ? formListName : [formListName]
  }, [formListName])

  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) => {
        if (record?.isSkeleton) {
          return (
            <Skeleton.Input active size='small' style={{ width: '100%' }} />
          )
        }
        return (
          <ObjectProperty
            {...property}
            longId={false}
            objectData={record}
            isEditing={isEditing}
          />
        )
      }
    }))

    return [...propertyColumns, ...additionalColumns]
  }, [resolvedProperties, additionalColumns, isEditing])

  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 =
    typeof rowKey === 'function' ? rowKey : (_record, index) => index

  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 []

    // Single summary row where each rollup value is placed under
    // the column that matches its `property` field.
    const summaryRow = {}

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

      if (rollup && typeof rollup.value === 'function') {
        try {
          const updatedObjectData = { ...objectData }
          console.log('UPDATED OBJECT DATA', value)
          updatedObjectData[property.name] = value
          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])

  const rollupColumns = useMemo(() => {
    return 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]}</Text>
              {rollupLabel && <Text type='secondary'>{rollupLabel}:</Text>}
            </Flex>
          )
        }
      }
    })
  }, [properties, rollups])

  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={loading}
        pagination={false}
        size={size}
        rowKey={resolvedRowKey}
        scroll={scrollConfig}
        locale={{ emptyText }}
        style={{ maxWidth }}
        className='rollup-table'
      />
    </div>
  ) : null

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

  // When editing and a Form.List name is provided, bind rows via Form.List
  // instead of the manual value/onChange mechanism.
  if (isEditing === true && formListName) {
    return (
      <Form.List name={formListName}>
        {(fields, { add }) => {
          const listDataSource = fields.map((field, index) => ({
            _field: field,
            _index: index,
            key: field.key
          }))
          const listColumns = resolvedProperties.map((property) => ({
            title: property.label || property.name,
            dataIndex: property.name,
            key: property.name,
            width: property.columnWidth || getDefaultWidth(property.type),
            render: (_text, record) => {
              const field = record?._field
              if (!field) return null

              // Resolve the most up-to-date row data for this index from the form
              let rowObjectData = undefined
              if (formInstance && listNamePath) {
                const namePath = [...listNamePath, field.name]
                rowObjectData = formInstance.getFieldValue(namePath)
              }
              return (
                <ObjectProperty
                  {...property}
                  // Bind directly to this list item + property via NamePath
                  name={[field.name, property.name]}
                  longId={false}
                  isEditing={true}
                  objectData={rowObjectData}
                />
              )
            }
          }))

          const listTable = (
            <Flex vertical>
              <div ref={mainTableWrapperRef}>
                <Table
                  dataSource={listDataSource}
                  columns={[...listColumns, ...additionalColumns]}
                  pagination={false}
                  size={size}
                  loading={loading}
                  rowKey={(record) => record.key ?? record._index}
                  scroll={scrollConfig}
                  locale={{ emptyText }}
                  className={hasRollups ? 'child-table-rollups' : 'child-table'}
                  {...tableProps}
                />
              </div>
              {rollupTable}
            </Flex>
          )

          const handleAddListItem = () => {
            const newItem = {}
            resolvedProperties.forEach((property) => {
              if (property?.name) {
                newItem[property.name] = null
              }
            })
            add(newItem)
          }

          return (
            <Card>
              <Flex vertical gap={'middle'}>
                <Flex justify={'space-between'}>
                  <Button>Actions</Button>
                  <Button
                    type='primary'
                    icon={<PlusIcon />}
                    onClick={handleAddListItem}
                  />
                </Flex>
                {listTable}
              </Flex>
            </Card>
          )
        }}
      </Form.List>
    )
  }

  if (isEditing === true) {
    return (
      <Card>
        <Flex vertical gap={'middle'}>
          <Flex justify={'space-between'}>
            <Button>Actions</Button>
            <Button
              type='primary'
              icon={<PlusIcon />}
              onClick={handleAddItem}
            />
          </Flex>
          {tableComponent}
        </Flex>
      </Card>
    )
  }

  return tableComponent
}

ObjectChildTable.propTypes = {
  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,
  isEditing: PropTypes.bool,
  formListName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  value: PropTypes.arrayOf(PropTypes.object),
  onChange: PropTypes.func,
  maxWidth: PropTypes.string,
  rollups: PropTypes.arrayOf(PropTypes.object),
  objectData: PropTypes.object
}

export default ObjectChildTable
