import React from 'react'
import { View, StyleSheet, Platform, LayoutChangeEvent } from 'react-native'
import { isEqual } from 'lodash'
import PropTypes from 'prop-types'
import { IObject } from 'interfaces'
import useOnWindowResizing from 'hooks/useOnWindowResizing'
import { BindingContextConsumerComponent } from 'components/ContextConsumerComponent'
import ActionWrapper from 'components/ActionWrapper'
import TableCell from './TableCell'
import {
  useContextRowHovering,
  useContextTableRowHeight,
  useContextFixedColumnWidth,
} from './context'
import { MIN_ROW_HEIGHT } from './styles'
import { type CellData } from './TableTypes'
import { type TableTheme } from './theme'

export type Row = {
  id: number
  cells: CellData[]
}

type TableRowContentProps = {
  tableObjectId: string
  data: Row
  theme: TableTheme
  hideFirstColumn: boolean
  fixed: boolean
}

type BaseTableRowProps = Omit<TableRowContentProps, 'tableObjectId'>

type TableRowProps = BaseTableRowProps & {
  tableObject: IObject
  datasource: { datasourceId: string; tableId: string }
}

const TableRowContent: React.FC<TableRowContentProps> = props => {
  const { data, theme, hideFirstColumn, tableObjectId, fixed } = props

  const { isHovering, clearHovering, setHovering } = useContextRowHovering(
    tableObjectId,
    data.id
  )

  // We save the row height in the context so that we can match the height of the fixed content (frozen first column) and the regular content
  const { onRowHeightChanged, rowHeight } = useContextTableRowHeight(
    tableObjectId,
    data.id,
    MIN_ROW_HEIGHT
  )

  const fixedContentWidth = useContextFixedColumnWidth(tableObjectId)

  const resizing = useOnWindowResizing(200)

  const style: object[] = [styles.row, theme.row]
  if (isHovering) {
    style.push(theme.rowHover)
  }

  const viewProps = {
    style,
    // TODO(michael-adalo): https://reactnative.dev/blog/2022/12/13/pointer-events-in-react-native
    ...(Platform.OS === 'web' && {
      onPointerEnter: () => {
        setHovering()
      },
      onPointerLeave: () => {
        clearHovering()
      },
    }),
  }

  const onLayout = (ev: LayoutChangeEvent) => {
    const { height } = ev.nativeEvent.layout
    onRowHeightChanged(height)
  }

  if (resizing && fixed) {
    // While resizing, we don't show the fixed content, to avoid layout shifts until the resize is done
    return null
  } else if (!resizing) {
    // While resizing, don't set minimum height styles on the non-fixed content, so that it can shrink and onLayout runs again
    viewProps.style.push({ minHeight: rowHeight })
  }

  const key = `row-${data.id}${fixed ? '--fixed' : ''}`

  return (
    <View {...viewProps} onLayout={onLayout} key={key}>
      {data.cells.map((cell: CellData, index) => {
        // When we render with "Freeze first column", the first column is rendered by the FixedColumn component// Rows of the non-fixed content still render the first column cell, but without content, to take up the same space
        const cellStyles: object[] = [styles.cell, cell.style]
        const isHiddenFirstColumn = index === 0 && hideFirstColumn
        if (isHiddenFirstColumn) {
          cellStyles.push({ opacity: 0, minWidth: fixedContentWidth })
        }

        return (
          <View key={cell.fieldId} style={cellStyles}>
            <TableCell cell={cell} theme={theme} />
          </View>
        )
      })}
    </View>
  )
}

const hasAction = (obj: IObject) =>
  Boolean(obj.actions && Object.keys(obj.actions).length > 0)

class TableRow extends BindingContextConsumerComponent<TableRowProps> {
  static defaultProps = {
    hideFirstColumn: false,
    fixed: false,
  }

  static childContextTypes = {
    getBinding: PropTypes.func,
    getBindings: PropTypes.func,
    getBindingsList: PropTypes.func,
  }

  getChildContext() {
    return {
      getBinding: this.getBinding,
      getBindings: this.getBindings,
      getBindingsList: this.getBindingsList,
    }
  }

  getBinding = (objectId: string) => {
    const { data, tableObject } = this.props
    const { getBinding } = this.context

    if (
      objectId === tableObject.id ||
      objectId === (tableObject.dataBinding && tableObject.dataBinding.id)
    ) {
      try {
        return { ...data, id: JSON.parse(tableObject.id) }
      } catch {
        return data
      }
    } else if (getBinding) {
      return getBinding(objectId)
    }
  }

  getBindings = () => {
    const { data, datasource } = this.props

    const { getBindings } = this.context

    let result = {}

    if (getBindings) {
      result = getBindings()
    }

    if (data) {
      const key = `${datasource.datasourceId}.${datasource.tableId}`
      result[key] = data.id
    }

    return result
  }

  getBindingsList = () => {
    const { getBindingsList } = this.context
    const { data } = this.props

    const bindingsList = getBindingsList()

    if (data) return bindingsList.concat([data.id])
    return bindingsList
  }

  render() {
    const { tableObject, theme, data, hideFirstColumn, fixed } = this.props
    const { component } = this.context

    const { id: tableObjectId } = tableObject
    const rowContentProps = {
      theme,
      data,
      hideFirstColumn,
      tableObjectId,
      fixed,
    }

    const row = <TableRowContent {...rowContentProps} />

    if (hasAction(tableObject)) {
      return (
        <ActionWrapper
          bindingData={{}}
          object={tableObject}
          component={component}
          hitSlop={0}
        >
          {row}
        </ActionWrapper>
      )
    }

    return row
  }
}

export const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
    alignItems: 'center',
    minHeight: MIN_ROW_HEIGHT,
  },
  cell: {
    flex: 1,
    alignItems: 'flex-start',
    padding: 10,
    paddingLeft: 20,
  },
  cellText: {
    textAlign: 'left',
    fontSize: 13,
    fontWeight: '500',
    lineHeight: 15.25,
  },
})

export default React.memo(TableRow, isEqual)
