import React, { ComponentType, useEffect, useState } from 'react'
import { Box, HStack, InputControl, ResetButton, Select, SubmitButton } from '@native-base/formik-ui'
import { Formik } from 'formik'
import { Button, Heading } from 'native-base'
import { observer } from 'mobx-react-lite'
import type { EntityModelType, FieldConfig } from './EntityModel'
import { emptyValues, isRequiredField, loadedValues, validationSchema, ViewType } from './EntityModel'
import { SelectControl } from './SelectControl'
import type { ObjectSchema } from 'yup'
import DateInput from './DateInput.web'
import type { NavigationProp, RouteProp } from '@react-navigation/native'
import AssocSingleInput from './AssocSingleInput'
import { LoadingSpinner, LOADING_LOAD_OPACITY } from './LoadingSpinner'
import { useQuery } from './store'

export type EntityFormProps<ModelType> = {
  // View config of the model type we handle
  model: EntityModelType

  // Root model for which the created/edited item belongs to via a relation.
  rootEntityModel: EntityModelType

  // If set, we are editing an existing object, otherwise creating a new one
  editId?: string | number | null

  // Called to generate view (input) for each field. If not set, uses default generator
  fieldViewGenerator?: (
    field: FieldConfig<ModelType>,
    obj: ModelType,
    validationSchema: ObjectSchema<any>,
    navigation: NavigationProp<any>,
    currentRoute: RouteProp<any>
  ) => ComponentType<any>

  // Called before saving/updating record
  beforeSave?: (rootModel: EntityModelType, model: EntityModelType, values: any) => void

  // Custom creation function doing the actual mutation to create a new object  provided by EntityForm
  create?: (rootModel: EntityModelType, model: EntityModelType, values: any) => void

  // Called after record is saved
  afterSave?: (savedItem: any) => void

  // Called when Cancel is clicked
  afterCancel?: () => void

  navigation: NavigationProp<any>

  currentRoute: RouteProp<any>
}

function defaultFieldViewGenerator(
  field: FieldConfig<any>,
  obj: any,
  validationSchema: ObjectSchema<any>,
  navigation: NavigationProp<any>,
  currentRoute: RouteProp<any>
) {
  // If we have a field specific input, use that
  if (field.inForm?.inputComponent) {
    return field.inForm?.inputComponent(field, obj, validationSchema)
  }

  if (field.inputTypeVal(obj) == 'assocSingle') {
    return (
      <AssocSingleInput
        field={field}
        navigation={navigation}
        parentRoute={currentRoute.name}
        validationSchema={validationSchema}
        obj={obj}
      />
    )
  }

  if (field.inputTypeVal(obj) == 'date') {
    return <DateInput name={field.name} label={field.displayNameVal(ViewType.FORM)} />
  }

  if (field.inputTypeVal(obj) == 'select') {
    return (
      <>
        <SelectControl
          isRequired={isRequiredField(field, validationSchema)}
          name={field.name}
          label={field.displayNameVal(ViewType.FORM)}
          mt={4}
        >
          {Object.entries(field.selectValuesVal(obj)).map((e) => {
            return <Select.Item label={e[1]} value={e[0]} />
          })}
        </SelectControl>
      </>
    )
  }

  return (
    <InputControl
      // @ts-ignore: Input.type exists
      type={field.inputTypeVal(obj)}
      mt={4}
      name={field.name}
      label={field.displayNameVal()}
      // @ts-ignore
      placeholder={field.displayNameVal(ViewType.FORM)}
      isRequired={isRequiredField(field, validationSchema)}
      isDisabled={field.viewInFormVal()}
    />
  )
}

export function createEntityForm<ModelType>() {
  const EntityForm: React.FC<EntityFormProps<ModelType>> = observer(
    ({
      model,
      rootEntityModel,
      editId,
      fieldViewGenerator,
      beforeSave,
      create,
      afterSave,
      afterCancel,
      navigation,
      currentRoute,
    }) => {
      const config = model.config
      const isCreateForm = !editId
      const { setQuery, loading, error } = useQuery()
      const [initialValues, setInitialValues] = useState(() => emptyValues(config, model.defaultCreateFieldValues))
      const [validation] = useState(() => validationSchema(config, isCreateForm))
      const [fieldViewGeneratorFunc] = useState(() =>
        fieldViewGenerator ? fieldViewGenerator : defaultFieldViewGenerator
      )
      const [loadingOpacity, setLoadingOpacity] = useState(LOADING_LOAD_OPACITY)

      // Make sure initialValues are updated when defaultCreateFieldValues changes
      useEffect(() => {
        if (isCreateForm) {
          setInitialValues(emptyValues(config, model.defaultCreateFieldValues))
        }
      }, [config, isCreateForm, model.defaultCreateFieldValues])

      useEffect(() => {
        if (editId) {
          // Load record for filling form with initial values in next useEffect
          model.load(editId.toString())
        } else {
          // Start with a clean form
          model.setLoadedItem(null)
          setInitialValues(emptyValues(config, model.defaultCreateFieldValues))
        }
      }, [config, editId, model])

      useEffect(() => {
        // If loadedItem arrived, fill form with its values
        if (model.loadedItem) {
          const initVal = {
            ...emptyValues(config),
            ...loadedValues(config, model.loadedItem),
          }
          setInitialValues(initVal)
        } else {
          setInitialValues(emptyValues(config, model.defaultCreateFieldValues))
        }
      }, [config, model.defaultCreateFieldValues, model.loadedItem])

      // When onSubmit is started show spinner
      useEffect(() => {
        if (loading) {
          setLoadingOpacity(LOADING_LOAD_OPACITY)
        } else {
          setLoadingOpacity(1)
        }
      }, [loading, error])

      const onSubmit = async (values) => {
        // If editing, update the record
        //console.log("+++ onSubmit values", values)
        let mut
        beforeSave && beforeSave(rootEntityModel, model, values)
        if (editId) {
          mut = model.update(values)
        }
        // Create a new record
        else {
          // Do we have a custom create()?
          if (create) {
            mut = create(rootEntityModel, model, values)
          }
          // Use default one.
          else {
            mut = model.create(values)
          }
        }

        setQuery(mut)
        mut.then(() => {
          if (afterSave) {
            // pass to the afterSave the created or updated item (these must have been set by
            // the entity model already)
            if (isCreateForm) {
              afterSave(model.createdItem)
            } else {
              afterSave(model.updatedItem)
            }
          }
        })
        return mut
      }

      function includeField(f: FieldConfig<ModelType>) {
        if (isCreateForm) {
          return f.editableAtCreationVal() || f.viewInFormAtCreationVal()
        }
        return f.editableVal() || f.viewInFormVal()
      }

      return (
        <Formik
          initialValues={initialValues}
          enableReinitialize={true}
          validateOnBlur={true}
          validateOnChange={false}
          onSubmit={onSubmit}
          validationSchema={validation}
        >
          <Box>
            {loadingOpacity != 1 ? (
              <>
                <LoadingSpinner />
              </>
            ) : null}
            <Heading>
              {editId
                ? model.loadedItem && config.editFormScreenTitleVal(model.loadedItem)
                : config.createFormScreenTitleVal()}
            </Heading>
            {config
              .fieldsVal()
              .filter(includeField)
              .map((f) =>
                fieldViewGeneratorFunc(f as FieldConfig<any>, model.loadedItem, validation, navigation, currentRoute)
              )}
            <HStack space={3} flex={1} mt={4}>
              <HStack space={3} flex={1}>
                <SubmitButton _text={{ color: 'white' }}>Mentés</SubmitButton>
                <ResetButton
                  bg="coolGray.500"
                  _text={{ color: 'white' }}
                  // @ts-ignore
                  onPress={() => {
                    afterCancel && afterCancel()
                  }}
                >
                  Mégse
                </ResetButton>
              </HStack>
              {!isCreateForm &&
                model.config.entityListScreenConfig!.onDeletePress &&
                model.loadedItem &&
                (!model.config.canDelete || model.config.canDelete(model.loadedItem, ViewType.FORM, model.config)) && (
                  <HStack justifyContent={'flex-end'} flex={1}>
                    <Button
                      colorScheme={'secondary'}
                      // @ts-ignore
                      onPress={(e) =>
                        model.config.entityListScreenConfig!.onDeletePress!(e, model.loadedItem, model.config)
                      }
                    >
                      Törlés
                    </Button>
                  </HStack>
                )}
            </HStack>
          </Box>
        </Formik>
      )
    }
  )
  //return Factory(EntityForm)
  return EntityForm
}
