import React, { ReactElement, useEffect, useMemo, useReducer, useState } from 'react'
import { Radio, Text } from '@native-base/formik-ui'
import { observer } from 'mobx-react-lite'
import { EntityConfig, EntityConfigAny, EntityModelType, QueryState, RelationModelType } from './EntityModel'
import { DataTable } from 'react-native-paper'
import { Box, Button, Checkbox, Factory, Heading, HStack, Input } from 'native-base'
import type { GestureResponderEvent } from 'react-native'
import {
  BaseFilter,
  buildFieldExp,
  calcFilterCond,
  fieldValue,
  SimpleFilter,
  TableSelectionType,
  ViewType,
} from './EntityModel'
import { MaterialIcons } from '@expo/vector-icons'
import { LoadingSpinner, LOADING_LOAD_OPACITY } from './LoadingSpinner'
import { useFocusEffect } from '@react-navigation/native'

const TTDataTable = Factory(DataTable)
const TTDataTableHeader = Factory(DataTable.Header)
const TTDataTableTitle = Factory(DataTable.Title)
const TTDataTableRow = Factory(DataTable.Row)
const TTDataTableCell = Factory(DataTable.Cell)
// This doesn't work
// const TTDataTablePagination = Factory(DataTable.Pagination)

export type EntityTableProps<ModelType> = {
  model?: EntityModelType

  // Set if the table is for listing relational data
  relation?: RelationModelType

  // Optionally set for relation listings
  fieldName?: string

  // If true, all elements of the table can be deleted, canDelete won't be called
  // Used mostly in case of m2m join tables, where the join can always be deleted
  canDeleteAll?: boolean

  /**
   * Called to generate actions for an item in the table. The Delete action is provided imlicitly, other actions
   * via eg. buttons can be provided here
   * @param item
   */
  actionGenerator?: (config: EntityConfigAny, item: ModelType) => ReactElement
}

/**
 * Common filter, sort and paging support for EntityModel based components.
 *
 * @param model
 * @param initialPageSize
 */
export function useEntityModel(model: EntityModelType, initialPageSize: number = 5) {
  const config = model.config
  const [page, setPage] = useState(model.currentPage)
  const [filter, setFilter] = useState('')
  const [simpleFilters, setSimpleFilters] = useState<BaseFilter<any>[]>([])
  const [sortField, setSortField] = useState('id')
  const [sortDirection, setSortDirection] = useState<'ascending' | 'descending'>('ascending')
  const [stringFields] = useState(() =>
    config
      .fieldsVal()
      .filter((f) => !f.numericVal() && f.searchableVal())
      .map((f) => f.searchAccessorVal())
  )
  const [numFields] = useState(() =>
    config
      .fieldsVal()
      .filter((f) => f.numericVal() && f.searchableVal())
      .map((f) => f.searchAccessorVal())
  )
  const [itemsFrom, setItemsFrom] = useState(0)
  const [itemsTo, setItemsTo] = useState(0)
  const [loadingOpacity, setLoadingOpacity] = useState(1)

  //     const from = page * model.pageSize;
  //     const to = Math.min((page + 1) * model.pageSize, model.count);
  // Initialize page size
  useEffect(() => {
    model.setPageSize(initialPageSize)
    applyFilter()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // Reload when filter or sort changes
  useEffect(() => {
    applyFilter()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filter, sortField, sortDirection, simpleFilters])

  // if page or the pageSize changes do a list on the new page or with the new pageSize
  useEffect(() => {
    let pageToList = page
    // If count is reset, go back to first page
    if (!model.count) {
      setPage(0)
      pageToList = 0
    }
    model.list(pageToList)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [page, model.pageSize, model.count])

  useEffect(() => {
    if (model.listQueryState == QueryState.DONE) {
      setItemsFrom(page * model.pageSize)
      setItemsTo(Math.min((page + 1) * model.pageSize, model.count!))
    }

    if (model.listQueryState == QueryState.PENDING || !model.listQueryState) {
      setLoadingOpacity(LOADING_LOAD_OPACITY)
    } else {
      setLoadingOpacity(1)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [model.listQueryState])

  // eslint-disable-next-line react-hooks/exhaustive-deps
  function applyFilter() {
    let cond: any = null
    // If we have a full text filter cal it first and may add simple filters
    if (filter.trim().length > 0) {
      const func = config.calcFilterCond || calcFilterCond
      cond = func(filter, stringFields, numFields)
      // AND free text cond with simple filters
      if (simpleFilters) {
        cond = {
          _and: [...simpleFilters.map((sf) => sf.condition), cond],
        }
      }
    }
    // No full text filter, use simpleFilters if any
    else if (simpleFilters.length) {
      cond = {
        _and: [...simpleFilters.map((sf) => sf.condition)],
      }
    }

    let sort = {}
    if (sortField) {
      const field = config.fieldsVal().find((f) => f.name == sortField)
      // sortAccessor returns either field.name or the value of field.sortAccessor if it is set
      let fieldToSort = field!.sortAccessorVal()
      sort = buildFieldExp(fieldToSort, sortDirection == 'ascending' ? 'asc' : 'desc')
    }
    model.setQueryParams(cond, [sort])
  }

  function updateSort(field: string) {
    if (sortField == field) {
      if (sortDirection == 'ascending') {
        setSortDirection('descending')
      } else {
        setSortDirection('ascending')
      }
    } else {
      setSortField(field)
      setSortDirection('ascending')
    }
  }

  return {
    page,
    setPage,
    itemsFrom,
    itemsTo,
    filter,
    setFilter,
    simpleFilters,
    setSimpleFilters,
    sortField,
    setSortField,
    sortDirection,
    setSortDirection,
    stringFields,
    numFields,
    updateSort,
    loadingOpacity,
  }
}

export function useLoadedItem(model: EntityModelType) {
  const [loadingOpacity, setLoadingOpacity] = useState(LOADING_LOAD_OPACITY)

  useEffect(() => {
    if (model.loadQueryState === QueryState.PENDING || !model.loadedItem) {
      setLoadingOpacity(LOADING_LOAD_OPACITY)
    } else {
      setLoadingOpacity(1)
    }
  }, [model.loadQueryState, model.loadedItem])

  return {
    loadingOpacity,
  }
}

/**
 * Creates an entity listing, editing component based on a ViewConfig.
 *
 * This factory is used so that we can properly type the component with a ModelType
 *
 */
export function createEntityTable<ModelType>(defaultProps: EntityTableProps<ModelType> | null = null) {
  const Table: React.FC<EntityTableProps<ModelType>> = observer((props) => {
    const [, forceUpdate] = useReducer((x) => x + 1, 0)
    // model must be set in either defaultProps at  createEntityTable() call time
    // or in props.model at instantiation time.
    const model = (props.model ? props.model : defaultProps ? defaultProps.model : null)!
    const canDeleteAll = props.canDeleteAll ? props.canDeleteAll : defaultProps ? defaultProps.canDeleteAll : false
    const actionGenerator = props.actionGenerator
      ? props.actionGenerator
      : defaultProps
      ? defaultProps.actionGenerator
      : null
    const relation = props.relation ? props.relation : defaultProps ? defaultProps.relation : null
    const config = model.config as EntityConfig<any, any, any, any>
    const [filterInput, setFilterInput] = useState('')
    const [hasActions, setHasActions] = useState(false)
    const {
      setPage,
      itemsFrom,
      itemsTo,
      setFilter,
      setSimpleFilters,
      sortField,
      sortDirection,
      updateSort,
      loadingOpacity,
    } = useEntityModel(model, 5)

    // Whenever we enter the screen with the EntityTable, makre sure selectedItems is emptied
    useFocusEffect(
      React.useCallback(() => {
        model.clearSlectedItems()
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, [])
    )

    // These may change dnamically so we need to update hasActions when these change
    useEffect(() => {
      const v =
        !!config.entityListScreenConfig.onDeletePress ||
        !!config.entityListScreenConfig.onUnassocPress ||
        !!config.entityListScreenConfig.generateActionComponent ||
        !!actionGenerator
      setHasActions(v)
    }, [
      config.entityListScreenConfig.onDeletePress,
      config.entityListScreenConfig.onUnassocPress,
      config.entityListScreenConfig.generateActionComponent,
      actionGenerator,
    ])

    // We create a snapshot of the original config.simpleFilters and will track filter check/uncheck
    // state here. Later we filter only the checked filters and pass it to setSimpleFilters
    const [simpleFiltersState, setSimpleFiltersState] = useState<SimpleFilter<any>[]>(() => {
      if (!config.entityListScreenConfig!.simpleFilters) {
        return []
      }
      // Make a snapshot of the filters and assign an ID to each filter. From now on we use the
      // ID to identify the filter. THis is also the value of the checkbox/radio button.
      const filters = JSON.parse(JSON.stringify(config.entityListScreenConfig!.simpleFilters)) as SimpleFilter<any>[]
      let id = 1
      filters.forEach((f) => {
        if (f.type == 'radio') {
          f.filters.map((rf) => {
            rf.id = id.toString()
            id++
          })
        } else {
          f.id = id.toString()
          id++
        }
      })
      return filters
    })

    // Update entity model query with checked simple filters
    useEffect(() => {
      let checkedFilters: BaseFilter<any>[] = []
      simpleFiltersState.forEach((f) => {
        if (f.type == 'radio') {
          f.filters.filter((rf) => rf.checked).forEach((rf) => checkedFilters.push(rf))
        } else if (f.checked) {
          checkedFilters.push(f)
        }
      })
      setSimpleFilters(checkedFilters)
      // Note: dependency is an array, to make change detection work we need ...spread
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...simpleFiltersState])

    // Mark the given filters as checked or unchecked
    function mark(idsAndChecked: { id: string; checked: boolean }[]) {
      const statesCopy = JSON.parse(JSON.stringify(simpleFiltersState))
      idsAndChecked.forEach(({ id, checked }) => {
        statesCopy.forEach((f) => {
          if (f.type == 'radio') {
            const r = f.filters.find((rf) => rf.id == id)
            if (r) {
              r.checked = checked
            }
          } else {
            if (f.id == id) {
              f.checked = checked
            }
          }
        })
      })
      setSimpleFiltersState(statesCopy)
    }

    // Update filter's value
    function handleFulltextFilterChange(event: any) {
      setFilterInput(event.target.value)
    }

    // Updates query params in the model based on the filter value
    function handleFulltextFilterEnter(event: any) {
      if (event.key === 'Enter') {
        setFilter(filterInput)
      }
    }

    // If canDeleteAll is not set, it defaults to false
    // const canDeleteAllValue =
    //   typeof canDeleteAll === 'undefined' ? false : canDeleteAll

    // Use memo as this is expensive. Recalc when simpleFiltersState changes
    const simpleFiltersView = useMemo(() => {
      let theView: any = null
      if (simpleFiltersState) {
        theView = (
          <HStack>
            {simpleFiltersState.map((f) => {
              if (f.type == 'radio') {
                // find the checked radio
                let checkedCond = f.filters.find((f) => f.checked)
                if (!checkedCond) {
                  checkedCond = f.filters[0]
                  checkedCond.checked = true
                }

                return (
                  <Radio.Group
                    value={checkedCond.id}
                    defaultValue={checkedCond.id}
                    name={f.name}
                    accessibilityLabel={f.ariaLabel}
                    onChange={(nextVal) => {
                      // uncheck previous
                      mark([
                        { id: checkedCond!.id!, checked: false },
                        { id: nextVal, checked: true },
                      ])
                    }}
                    key={f.ariaLabel}
                  >
                    <HStack>
                      {f.filters.map((rf) => {
                        return (
                          <Radio value={rf.id!} mx={2} key={rf.id}>
                            {rf.label}
                          </Radio>
                        )
                      })}
                    </HStack>
                  </Radio.Group>
                )
              }
              // checkbox
              else {
                return (
                  <Checkbox
                    value={f.id!}
                    my={2}
                    isChecked={f.checked}
                    onChange={(checked) => {
                      mark([{ id: f.id!, checked }])
                    }}
                    key={f.id}
                  >
                    {f.label}
                  </Checkbox>
                )
              }
            })}
          </HStack>
        )
      }
      return theView

      // Note: dependency is an array, to make change detection work we need ...spread
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...simpleFiltersState])

    // const hasActions = config.entityListScreenConfig.onDeletePress || config.entityListScreenConfig.generateActionComponent || actionGenerator
    // console.log("+++ canDelete hasActions", hasActions)
    // console.log("+++ canDelete model", model)
    // @ts-ignore
    return (
      <Box>
        <HStack alignItems={'center'} space={3}>
          <Heading>{config.title}</Heading>
          {/*{model.listQueryState == QueryState.PENDING && <Text>Loading</Text>}*/}
          {model.listQueryState == QueryState.ERROR && <Text>{JSON.stringify(model.listQueryError)}</Text>}
        </HStack>
        <HStack pt={3} alignItems={'center'}>
          <Input
            placeholder={'keresett szó'}
            w={'40%'}
            value={filterInput}
            onChange={handleFulltextFilterChange}
            onKeyPress={handleFulltextFilterEnter}
          />
          {simpleFiltersView}
          <Box flex={2} alignItems={'flex-end'}>
            {config.entityListScreenConfig.onNewPress && (
              <Button
                // @ts-ignore
                onPress={(event) => config.entityListScreenConfig.onNewPress!(event, config)}
                leftIcon={<MaterialIcons name="add" size={20} color="white" />}
              />
            )}
            {config.entityListScreenConfig.onAssocPress && (
              <Button
                // @ts-ignore
                onPress={(event) => config.entityListScreenConfig.onAssocPress!(event, config)}
                leftIcon={<MaterialIcons name="add-link" size={20} color="white" />}
              />
            )}
          </Box>
        </HStack>
        {
          // @ts-ignore
          config.entityListScreenConfig.tableComponent ? (
            config.entityListScreenConfig.tableComponent(config)
          ) : (
            <TTDataTable>
              <>{loadingOpacity != 1 && model.pageResult.length > 0 ? <LoadingSpinner /> : null}</>
              <TTDataTableHeader {...config.headerPropsVal()} opacity={loadingOpacity}>
                <>
                  {config
                    .fieldsVal()
                    .filter((f) => f.listableVal())
                    .map((f) =>
                      f.tableProps?.titleComponent ? (
                        f.tableProps.titleComponent(f)
                      ) : (
                        <TTDataTableTitle
                          sortDirection={sortField == f.name ? sortDirection : undefined}
                          onPress={() => (f.sortableVal() ? updateSort(f.name) : null)}
                          px={2}
                          flex={f.columnWidthFlexVal()}
                        >
                          <Text>{f.displayNameVal(ViewType.LISTING)}</Text>
                        </TTDataTableTitle>
                      )
                    )}
                </>
                <>
                  {hasActions && (
                    <TTDataTableTitle>
                      <Text>Műveletek</Text>
                    </TTDataTableTitle>
                  )}
                </>
              </TTDataTableHeader>
              <>
                {loadingOpacity != 1 && model.pageResult.length == 0 ? (
                  <HStack>
                    <LoadingSpinner />
                  </HStack>
                ) : (
                  model.pageResult.map((item: any) =>
                    config.entityListScreenConfig.rowComponent ? (
                      config.entityListScreenConfig.rowComponent(config, item)
                    ) : (
                      <TTDataTableRow
                        key={item.id}
                        // @ts-ignore
                        onPress={(event: GestureResponderEvent) => {
                          // hamster user can configure any action for onRowPress
                          config.entityListScreenConfig.onRowPress &&
                            config.entityListScreenConfig.onRowPress(event, item, config)

                          // Our internal logic for onSelection
                          if (config.entityListScreenConfig.onSelection) {
                            if (config.listScreenSelectionTypeVal() == TableSelectionType.SINGLE) {
                              config.entityListScreenConfig.onSelection(event, item, config)
                              model.addSelectedItem(item)
                            } else {
                              if (model.isSelectedItem(item)) {
                                model.removeSelectedItem(item)
                              } else {
                                model.addSelectedItem(item)
                              }
                            }
                          }
                          // We could use some more exact mechanism like storing the selectedItems in userState state and a useEffect
                          // to listen changes, but that would be much more logic and with no real value added. So instead, here we just
                          // force update via this useReducer hack
                          forceUpdate()
                        }}
                        {...config.rowPropsVal(item)}
                        opacity={loadingOpacity}
                        bg={model.isSelectedItem(item) ? 'primary.50' : null}
                      >
                        <>
                          {config
                            .fieldsVal()
                            .filter((f) => f.listableVal())
                            .map((f) =>
                              f.tableProps?.cellComponent ? (
                                f.tableProps?.cellComponent(f, item)
                              ) : (
                                <TTDataTableCell px={2} flex={f.columnWidthFlexVal()} {...f.cellPropsVal(item)}>
                                  <Text {...f.textPropsVal(item)}>{fieldValue(f, ViewType.LISTING, item)}</Text>
                                </TTDataTableCell>
                              )
                            )}
                        </>
                        <>
                          {hasActions && (
                            <TTDataTableCell>
                              <HStack space={2}>
                                {config.entityListScreenConfig.onDeletePress && (
                                  <Button
                                    isDisabled={
                                      (canDeleteAll !== undefined && canDeleteAll === false) ||
                                      (canDeleteAll === undefined &&
                                        config.canDelete != null &&
                                        !config.canDelete(item, ViewType.LISTING, config))
                                    }
                                    colorScheme={'secondary'}
                                    leftIcon={<MaterialIcons name="delete" size={20} color="white" />}
                                    // @ts-ignore
                                    onPress={(e) => config.entityListScreenConfig.onDeletePress!(e, item, config)}
                                  />
                                )}
                                {config.entityListScreenConfig.onUnassocPress &&
                                  !!config.entityModel.canUnassociateAny && (
                                    <Button
                                      isDisabled={
                                        relation && relation.canUnassociate
                                          ? !relation.canUnassociate(item, ViewType.LISTING, relation.fieldModel)
                                          : config.canUnassociate != null &&
                                            !config.canUnassociate(item, ViewType.LISTING, config)
                                      }
                                      colorScheme={'secondary'}
                                      leftIcon={<MaterialIcons name="link-off" size={20} color="white" />}
                                      // @ts-ignore
                                      onPress={(e) => config.entityListScreenConfig.onUnassocPress!(e, item, config)}
                                    />
                                  )}

                                {config.entityListScreenConfig.generateActionComponent &&
                                  config.entityListScreenConfig.generateActionComponent(config, item)}
                                {actionGenerator && actionGenerator(config, item)}
                              </HStack>
                            </TTDataTableCell>
                          )}
                        </>
                      </TTDataTableRow>
                    )
                  )
                )}
              </>
              <DataTable.Pagination
                page={model.currentPage}
                numberOfPages={model.numberOfPages}
                onPageChange={(page) => setPage(page)}
                label={`${itemsFrom + 1}-${itemsTo} / ${model.count}`}
                numberOfItemsPerPage={model.pageSize}
                numberOfItemsPerPageList={[5, 10, 15]}
                onItemsPerPageChange={(size) => {
                  setPage(0)
                  model.setPageSize(size)
                }}
                showFastPaginationControls
              />
            </TTDataTable>
          )
        }
        {config.listScreenSelectionTypeVal() == TableSelectionType.MULTIPLE && (
          <HStack>
            <Button
              isDisabled={model.selectedItems.length == 0}
              // @ts-ignore
              onPress={(event) => {
                if (config.entityListScreenConfig.onSelection) {
                  config.entityListScreenConfig.onSelection(event, model.selectedItems, config)
                }
              }}
            >
              OK
            </Button>
          </HStack>
        )}
      </Box>
    )
  })

  // return Factory(Table)
  return Table
}

export type EntityTableType = ReturnType<typeof createEntityTable>

// function routeValue<T, ModelType>(route: T | ((config: ViewConfig<ModelType>) => T), config: ViewConfig<ModelType>) : T
// {
//   if (typeof route === 'function') {
//     return (route as ((config: ViewConfig<ModelType>) => T))(config)
//   }
//   return route
// }
