import _defineProperty from "@babel/runtime/helpers/defineProperty";
// Copyright 1999-2023. Plesk International GmbH. All rights reserved.

import React, { Component, useContext } from 'react';
import { scrollIntoView, safeInvoke } from '../utils';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { CLS_PREFIX } from '../../constants';
import { SectionItem } from '../Section';
import Hint from '../Hint';
import Button from '../Button';
import Link from '../Link';
import Icon from '../Icon';
import Popover from '../Popover';
import Action from '../Action';
import Translate from '../Translate';
import Focuser from '../Overlay/Focuser';
import FormContext from '../Form/FormContext';
import VerticalContext from '../VerticalContext';
import ScrollableElementFormContext from '../Form/ScrollableElementFormContext';
import DisabledContext from '../Form/DisabledContext';
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
const defaultValue = undefined;

/**
 * Simple helper to handle situations when we expecting only array
 * @param value
 * @param nonEmpty
 * @returns {Array}
 */
const getArrayHelper = function (value) {
  let nonEmpty = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  if (Array.isArray(value) && value.length > 0) {
    return value;
  }
  const result = [];
  if (nonEmpty) {
    result.push(undefined);
  }
  return result;
};
const isControlled = props => props.value !== undefined;
const isEmpty = value => !value && value !== 0 || Array.isArray(value) && value.length === 0 || !Array.isArray(value) && typeof value === 'object' && Object.values(value).filter(t => Boolean(t)).length === 0;

/**
 * @since 0.0.54
 */
class FormField extends Component {
  constructor(_props) {
    var _this;
    super(_props);
    _this = this;
    /**
     * Field API provided to field
     * @param index
     * @returns {Object}
     */
    _defineProperty(this, "getFieldApi", index => {
      if (!this.props.multi) {
        return this.fieldApi;
      }
      return {
        getId: () => {
          const id = this.fieldApi.getId();
          return id ? `${id}-${index}` : id;
        },
        getName: () => {
          const name = this.fieldApi.getName();
          return name ? `${name}[${index}]` : name;
        },
        getValue: def => {
          const values = this.fieldApi.getValue();
          if (values instanceof Array && values[index]) {
            return values[index];
          }
          return def;
        },
        setValue: value => {
          const values = getArrayHelper(this.fieldApi.getValue(), true);
          return this.fieldApi.setValue(values.map((v, i) => i === index ? value : v));
        },
        getErrors: () => {
          const errors = this.fieldApi.getErrors();
          if (errors instanceof Array && errors[index]) {
            return errors[index];
          }
          return {};
        },
        getRequiredMark: () => {
          if (index === 0) {
            return this.fieldApi.getRequiredMark();
          }
          return null;
        },
        getSeparator: () => /*#__PURE__*/_jsx("span", {
          className: `${this.props.baseClassName}__separator`
        }),
        isDisabled: () => this.props.disabled,
        getFullDescription: () => {
          if (index === 0) {
            return this.fieldApi.getFullDescription();
          }
          return null;
        }
      };
    });
    _defineProperty(this, "focus", errors => {
      let index = 0;
      if (Array.isArray(errors)) {
        index = errors.findIndex(v => Boolean(v));
      }
      const {
        scrollableElement
      } = this.props;
      const target = this.targetRefs[index];
      if (target) {
        scrollIntoView(target, {
          boundary: scrollableElement
        });
        this.focusTimer = setTimeout(() => {
          if (target) {
            new Focuser().focusFirstDescendant(target);
          }
        }, 500);
      }
    });
    /**
     * Handler for clicks on 'Add more'
     */
    _defineProperty(this, "handleAddMore", () => {
      const values = getArrayHelper(this.fieldApi.getValue(), true);
      this.fieldApi.setValue([...values, defaultValue]);
    });
    /**
     * Handler for clicks on 'Remove'
     * @param index
     */
    _defineProperty(this, "handleRemove", index => {
      const values = getArrayHelper(this.fieldApi.getValue());
      this.fieldApi.setValue(values.filter((value, valueIndex) => valueIndex !== index));

      // Can be used for removing errors from form properties
      safeInvoke(this.props.onRemoveRow, index);

      // Remove errors from component state
      this.setState(prevState => {
        if (prevState.errors && prevState.errors.filter) {
          return {
            errors: prevState.errors.filter((error, errorIndex) => errorIndex !== index)
          };
        }
        return {};
      });
    });
    _defineProperty(this, "setTargetRef", index => node => {
      this.targetRefs[index] = node;
    });
    /**
     * Renders 'Remove' button for each row
     * @param index
     * @returns {null|React.ReactElement}
     */
    _defineProperty(this, "renderRemoveButton", index => {
      const {
        multi,
        locale,
        disabled
      } = this.props;
      const value = getArrayHelper(this.fieldApi.getValue());
      const isClearable = multi && value.length === 1 && !isEmpty(value[0]);
      if (!multi || value.length < 1 || value.length === 1 && isEmpty(value[0])) {
        return null;
      }
      return /*#__PURE__*/_jsx(Button, {
        className: `${this.props.baseClassName}__remove`,
        icon: isClearable ? 'clean' : 'cross-mark',
        tooltip: /*#__PURE__*/_jsx(Translate, {
          namespace: "FormField",
          content: isClearable ? 'clear' : 'remove',
          fallback: isClearable ? 'Clear' : 'Remove',
          translators: locale
        }),
        onClick: () => this.handleRemove(index),
        disabled: disabled,
        ghost: true
      });
    });
    /**
     * Renders description for last row
     * @param index
     * @returns {null|React.ReactElement}
     */
    _defineProperty(this, "renderDescription", index => {
      const {
        baseClassName,
        description,
        multi
      } = this.props;
      if (!description || multi && getArrayHelper(this.fieldApi.getValue()).length - 1 > index) {
        return null;
      }
      return /*#__PURE__*/_jsx(Hint, {
        component: "div",
        className: `${baseClassName}__description`,
        children: description
      });
    });
    /**
     * Renders one field
     * @param index
     * @returns {React.ReactElement}
     */
    _defineProperty(this, "renderField", function () {
      let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
      let renderAddMore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
      const {
        baseClassName,
        className,
        id,
        name,
        value,
        onChange,
        label: formFieldLabel,
        description,
        errors,
        required,
        children,
        multi,
        disabled,
        addMoreValidator,
        onRemoveRow,
        locale,
        form,
        vertical,
        scrollableElement,
        fullDescription,
        ...props
      } = _this.props;
      const fieldApi = _this.getFieldApi(index);
      const fieldErrors = fieldApi.getErrors();
      const hasErrors = fieldErrors && Object.keys(fieldErrors).length > 0;
      let label = formFieldLabel;
      if (label && index) {
        label = '';
      }
      let innerField = typeof children === 'function' ? children(fieldApi) : children;
      if (multi) {
        innerField = /*#__PURE__*/_jsxs("div", {
          className: `${baseClassName}__multi`,
          children: [innerField, ` `, _this.renderRemoveButton(index)]
        });
      }
      return /*#__PURE__*/_jsxs(SectionItem, {
        className: classNames(baseClassName, {
          [`${baseClassName}--error`]: hasErrors,
          [`${baseClassName}--vertical-multi`]: multi && vertical
        }, className),
        title: label === null ? null : /*#__PURE__*/_jsxs("div", {
          className: `${baseClassName}__label`,
          children: [/*#__PURE__*/_jsx("label", {
            htmlFor: fieldApi.getId(),
            children: label
          }), fieldApi.getFullDescription(), required ? fieldApi.getRequiredMark() : null]
        }),
        vertical: vertical,
        ...props,
        children: [/*#__PURE__*/_jsx("div", {
          ref: _this.setTargetRef(index),
          children: innerField
        }), hasErrors && Object.keys(fieldErrors).map(key => /*#__PURE__*/_jsxs("span", {
          className: `${baseClassName}__error`,
          children: [/*#__PURE__*/_jsx(Icon, {
            className: `${baseClassName}__error-icon`,
            name: "exclamation-mark-circle-filled",
            size: "12"
          }), fieldErrors[key]]
        }, key)), _this.renderDescription(index), renderAddMore && /*#__PURE__*/_jsx(Link, {
          role: "button",
          onClick: _this.handleAddMore,
          className: `${baseClassName}__add-more`,
          disabled: disabled,
          children: /*#__PURE__*/_jsx(Translate, {
            namespace: "FormField",
            content: "addMore",
            fallback: "Add one more",
            translators: locale
          })
        })]
      }, index);
    });
    const {
      value: _value,
      multi: _multi
    } = this.props;
    let valueInState = _value;
    if (_multi && !valueInState) {
      valueInState = [defaultValue];
    }

    // eslint-disable-next-line react/state-in-constructor
    this.state = {
      value: valueInState,
      errors: this.props.errors
    };
    this.fieldApi = {
      getId: () => {
        if (this.props.id) {
          return this.props.id;
        }
        if (this.props.name) {
          return this.props.name.replace(/[[\]]+/g, '-').replace(/-+$/, '');
        }
        return null;
      },
      getName: () => this.props.name,
      getValues: () => {
        if (this.props.form) {
          return this.props.form.getValues();
        }
        return null;
      },
      getValue: def => {
        if (this.props.form && this.props.name) {
          return this.props.form.getValue(this.props.name, def);
        }
        return this.state.value || def;
      },
      setValue: value => {
        this.setState({
          value
        });
        if (this.props.form && this.props.name) {
          this.props.form.setValue(this.props.name, value);
        }
        safeInvoke(this.props.onChange, value);
      },
      getErrors: () => {
        if (this.props.form && this.props.name) {
          return this.props.form.getErrors(this.props.name);
        }
        return this.state.errors;
      },
      getRequiredMark: () => {
        if (this.props.form) {
          return this.props.form.getRequiredMark();
        }
        return null;
      },
      isDisabled: () => Boolean(this.props.disabled),
      getFullDescription: () => {
        if (!this.props.fullDescription) {
          return null;
        }
        return /*#__PURE__*/_jsx(Popover, {
          target: /*#__PURE__*/_jsx(Action, {
            outerProps: {
              className: `${this.props.baseClassName}__full-description`
            },
            icon: {
              name: 'info-circle',
              size: '12',
              className: `${this.props.baseClassName}__full-description-icon`
            }
          }),
          placement: "right",
          intent: "info",
          children: this.props.fullDescription
        });
      }
    };
    this.targetRefs = [];
  }
  static getDerivedStateFromProps(props, state) {
    const nextState = {};
    const {
      errors,
      form,
      required,
      name
    } = props;
    const {
      prevErrors,
      prevRequired
    } = state;
    if (isControlled(props)) {
      nextState.value = props.value;
    }
    if (errors !== prevErrors) {
      nextState.errors = errors;
    }
    if (form && required !== prevRequired) {
      form.setRequiredField(name, required);
    }
    nextState.prevErrors = errors;
    nextState.prevRequired = required;
    return nextState;
  }
  componentDidMount() {
    if (this.props.form) {
      this.props.form.setRequiredField(this.props.name, this.props.required);
      this.props.form.registerField(this.props.name, {
        focus: this.focus
      });
    }
  }
  componentWillUnmount() {
    if (this.props.form) {
      this.props.form.setRequiredField(this.props.name, false);
      this.props.form.unregisterField(this.props.name);
    }
    clearTimeout(this.focusTimer);
  }
  render() {
    if (!this.props.multi) {
      return this.renderField();
    }
    const max = this.props.multi.max || Infinity;
    let index = 0;
    const count = (this.fieldApi.getValue() || []).length || 1;
    const lines = [];
    do {
      lines.push(this.renderField(index++, count === index && count < max));
    } while (index < count);
    return lines;
  }
}
const propTypes = {
  /**
   * Field ID.
   * @since 0.0.59
   */
  id: PropTypes.string,
  /**
   * Field name.
   * @since 0.0.54
   */
  name: PropTypes.string,
  /**
   * Field value.
   * @deprecated Use [Form](#!/Form) values.
   * @since 0.0.57
   */
  value: PropTypes.any,
  /**
   * A callback function, can be executed when the value is changing.
   * @since 0.4.1
   */
  onChange: PropTypes.func,
  /**
   * Field label.
   * @since 0.0.54
   */
  label: PropTypes.any,
  /**
   * Field description.
   * @since 0.0.57
   */
  description: PropTypes.any,
  /**
   * Show long descriptions in popover after clicking on "( i )" icon
   * @since 3.14.0
   */
  fullDescription: PropTypes.node,
  /**
   * Field errors.
   *  @deprecated Use [Form](#!/Form) errors.
   * @since 0.0.57
   */
  errors: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  /**
   * Indicates whether field is required.
   * @since 0.0.54
   */
  required: PropTypes.bool,
  /**
   * Content of the `FormField`.
   * @since 0.0.54
   */
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  /**
   * If 'true', a field will be multiple. You can also limit the number of fields with a value like { max: 5 }.
   * @since 1.12.0
   */
  multi: PropTypes.oneOfType([PropTypes.bool, PropTypes.shape({
    max: PropTypes.number.isRequired
  })]),
  /**
   * @ignore Disabled state of multiple field.
   */
  disabled: PropTypes.bool,
  /**
   * Localization messages
   * @since 1.12.0
   */
  locale: PropTypes.shape({
    addMore: PropTypes.node,
    remove: PropTypes.node
  }),
  /**
   * Callback which called for all values. If it returns 'true' a pseudo-link 'Add more' will be disabled
   * @ignore
   * @deprecated
   */
  addMoreValidator: PropTypes.func,
  /**
   * A callback function, can be executed when the row is removed.
   * @since 1.12.0
   */
  onRemoveRow: PropTypes.func,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * @ignore
   */
  baseClassName: PropTypes.string,
  /**
   * @ignore
   */
  form: PropTypes.object,
  /**
   * Vertical or horizontal field view.
   * If it is not defined explicitly, the vertical mode will use from Form or Section components.
   * @since 3.13.0
   */
  vertical: PropTypes.bool,
  /**
   * @ignore
   */
  scrollableElement: PropTypes.oneOfType([PropTypes.instanceOf(Element), PropTypes.instanceOf(Window)])
};
const BASE_FORM_FIELD_CLASS_NAME = `${CLS_PREFIX}form-field`;
const defaultProps = {
  id: undefined,
  name: undefined,
  value: defaultValue,
  onChange: null,
  label: undefined,
  description: undefined,
  errors: {},
  required: false,
  children: undefined,
  multi: undefined,
  disabled: undefined,
  locale: undefined,
  addMoreValidator: undefined,
  onRemoveRow: null,
  fullDescription: undefined,
  className: undefined,
  baseClassName: BASE_FORM_FIELD_CLASS_NAME,
  form: undefined,
  vertical: undefined,
  scrollableElement: undefined
};
FormField.propTypes = propTypes;
FormField.defaultProps = defaultProps;

/**
 * `FormField` component is used for showing of parameter values in Form or
 * for entering of user data with any form elements.
 *
 * For the most cases the following form elements can be used:
 * * [FormFieldText](#!/FormFieldText) for entering text,
 * * [FormFieldSelect](#!/FormFieldSelect), [FormFieldCheckbox](#!/FormFieldCheckbox),
 * [FormFieldRadioButtons](#!/FormFieldRadioButtons) for selecting options.
 */

const FormFieldWrapper = props => {
  const form = useContext(FormContext);
  const verticalContext = useContext(VerticalContext);
  const vertical = props.vertical === undefined ? verticalContext : props.vertical;
  const scrollableElement = useContext(ScrollableElementFormContext);
  const disabled = useContext(DisabledContext);
  return /*#__PURE__*/_jsx(FormField, {
    ...props,
    form: form,
    vertical: vertical,
    scrollableElement: scrollableElement,
    disabled: props.disabled || disabled
  });
};
FormFieldWrapper.displayName = 'FormField';
FormFieldWrapper.propTypes = propTypes;
FormFieldWrapper.defaultProps = defaultProps;
export default FormFieldWrapper;
export { BASE_FORM_FIELD_CLASS_NAME };