content: //@flow
  import PropTypes from 'prop-types'
  import React from 'react'
  import FormBuilderPropTypes from '../../FormBuilderPropTypes'
  import Field from './Field'
  import Fieldset from 'part:@lyra/components/fieldsets/default'
  import PatchEvent, {set, setIfMissing, unset} from '../../PatchEvent'
  import isEmpty from '../../utils/isEmpty'
  import UnknownFields from './UnknownFields'
  import fieldStyles from './styles/Field.css'
  
  function getCollapsedWithDefaults(options = {}, level) {
    // todo: warn on "collapsable" and deprecate collapsible in favor of just "collapsed"
    //       --> relevant: https://github.com/lyra-io/lyra/issues/537
    if (options.collapsible === true || options.collapsable === true) {
      // collapsible explicit set to true
      return {
        collapsible: true,
        collapsed: options.collapsed !== false
      }
    } else if (options.collapsible === false || options.collapsable === false) {
      // collapsible explicit set to false
      return {
        // hard limit to avoid infinite recursion
        collapsible: level > 9,
        collapsed: level > 9
      }
    }
  
    // default
    return {
      collapsible: level > 2,
      collapsed: level > 2
    }
  }
  
  export default class ObjectInput extends React.PureComponent {
    static propTypes = {
      type: FormBuilderPropTypes.type,
      value: PropTypes.object,
      onChange: PropTypes.func,
      onFocus: PropTypes.func.isRequired,
      focusPath: PropTypes.array,
      markers: PropTypes.array,
      onBlur: PropTypes.func.isRequired,
      level: PropTypes.number,
      readOnly: PropTypes.bool,
      isRoot: PropTypes.bool
    }
  
    static defaultProps = {
      onChange() {},
      level: 0,
      focusPath: [],
      isRoot: false
    }
  
    handleBlur() {
      const {onChange, value} = this.props
      if (isEmpty(value)) {
        onChange(PatchEvent.from(unset()))
      }
    }
  
    handleFieldChange = (fieldEvent: PatchEvent, field) => {
      const {onChange, type, value, isRoot} = this.props
  
      let event = fieldEvent.prefixAll(field.name)
  
      if (!isRoot) {
        event = event.prepend(
          setIfMissing(type.name === 'object' ? {} : {_type: type.name})
        )
        if (value) {
          const valueTypeName = value && value._type
          const schemaTypeName = type.name
  
          // eslint-disable-next-line max-depth
          if (valueTypeName && schemaTypeName === 'object') {
            // The value has a _type key, but the type name from schema is 'object',
            // but _type: 'object' is implicit so we should fix it by removing it
            event = event.prepend(unset(['_type']))
          } else if (
            schemaTypeName !== 'object' &&
            valueTypeName !== schemaTypeName
          ) {
            // There's a mismatch between schema type and the value _type
            // fix it by setting _type to type name defined in schema
            event = event.prepend(set(schemaTypeName, ['_type']))
          }
        }
      }
      onChange(event)
    }
  
    renderField(field, level, index) {
      if (field.type.hidden) {
        return null
      }
  
      const {value, markers, readOnly, focusPath, onFocus, onBlur} = this.props
      const fieldValue = value && value[field.name]
      return (
        <Field
          key={field.name}
          field={field}
          value={fieldValue}
          onChange={this.handleFieldChange}
          onFocus={onFocus}
          onBlur={onBlur}
          markers={markers}
          focusPath={focusPath}
          level={level}
          readOnly={readOnly}
          ref={index === 0 && this.setFirstField}
        />
      )
    }
  
    renderFieldset(fieldset, fieldsetIndex) {
      const {level, focusPath} = this.props
      const columns = fieldset.options && fieldset.options.columns
  
      const collapsibleOpts = getCollapsedWithDefaults(fieldset.options, level)
  
      const isExpanded =
        focusPath.length > 0 &&
        fieldset.fields.some(field => focusPath[0] === field.name)
  
      return (
        <div key={fieldset.name} className={fieldStyles.root}>
          <Fieldset
            legend={fieldset.title}
            description={fieldset.description}
            level={level + 1}
            columns={columns}
            isCollapsible={collapsibleOpts.collapsible}
            isCollapsed={!isExpanded && collapsibleOpts.collapsed}
          >
            {fieldset.fields.map((field, fieldIndex) => {
              return this.renderField(
                field,
                level + 2,
                fieldsetIndex + fieldIndex
              )
            })}
          </Fieldset>
        </div>
      )
    }
  
    getRenderedFields() {
      const {type, level} = this.props
  
      if (!type.fieldsets) {
        // this is a fallback for schema types that are not parsed to be objects, but still has jsonType == 'object'
        return (type.fields || []).map((field, i) =>
          this.renderField(field, level + 1, i)
        )
      }
  
      return type.fieldsets.map((fieldset, i) => {
        return fieldset.single
          ? this.renderField(fieldset.field, level + 1, i)
          : this.renderFieldset(fieldset, i)
      })
    }
  
    renderUnknownFields() {
      const {value, type, onChange, readOnly} = this.props
      if (!type.fields) {
        return null
      }
  
      const knownFieldNames = type.fields.map(field => field.name)
      const unknownFields = Object.keys(value || {}).filter(
        key => !key.startsWith('_') && !knownFieldNames.includes(key)
      )
  
      if (unknownFields.length === 0) {
        return null
      }
  
      return (
        <UnknownFields
          readOnly={readOnly}
          fieldNames={unknownFields}
          value={value}
          onChange={onChange}
        />
      )
    }
  
    setFirstField = el => {
      this._firstField = el
    }
  
    focus() {
      if (this._firstField) {
        this._firstField.focus()
      }
    }
  
    render() {
      const {type, level, focusPath} = this.props
  
      const renderedFields = this.getRenderedFields()
      const renderedUnknownFields = this.renderUnknownFields()
  
      if (level === 0) {
        return (
          <div>
            {renderedFields}
            {renderedUnknownFields}
          </div>
        )
      }
  
      const collapsibleOpts = getCollapsedWithDefaults(type.options, level)
      const isExpanded = focusPath.length > 0
  
      const columns = type.options && type.options.columns
  
      return (
        <Fieldset
          level={level}
          legend={type.title}
          description={type.description}
          columns={columns}
          isCollapsible={collapsibleOpts.collapsible}
          isCollapsed={!isExpanded && collapsibleOpts.collapsed}
        >
          {renderedFields}
          {renderedUnknownFields}
        </Fieldset>
      )
    }
  }
  