var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { InjectVegaSlimmer, MapToComponentField, MapToComponentMethod, VegaSlimmer } from 'vega-slimmer/core';
import { ImmutableMap } from '../../../../helpers/immutable/immutable-map';
import { isElementVisible, isParent } from '../../../../utils/ui';
import { Observer } from '../../../../helpers/change-manager/observer/observer';
import ChangeManager from '../../../../helpers/change-manager/change-manager';
import { FORM_ELEMENT_VALIDATE, FORM_ELEMENT_VALUE_CHANGE, FORM_FIELD_ELEMENT_DISCONNECTED, FORM_FIELD_PENDING_VALID_CHANGE, } from '../../../../helpers/change-manager/subject/static-subject/static-subject-title';
import { FormFieldControllerSlimmer } from '../../../../helpers/slimmers/form-field-controller-slimmer';
import { ChildNodesNotifyObserverSlimmer, } from '../../../../helpers/event-manager/slimmers/child-nodes-notify-observer-slimmer';
import { isObjectArrayLike } from '../../../../utils/array';
import domNodeSubjectFactory from '../../../../helpers/change-manager/subject/dom-node-subject/dom-node-subject-factory';
import { VegaInternalValid } from '../../../../helpers/event-manager/event-id/vega-internal-event-id';
/**
 * The form field controller for vega form to manage the form field states.
 */
export class VegaFormFieldController extends VegaSlimmer {
  constructor() {
    super(...arguments);
    this.childNotifyObserver = new ChildNodesNotifyObserverSlimmer(FORM_ELEMENT_VALIDATE, (input) => this.updateInternalValidStatus(input));
    this.childNodeValueChangeObserver = new ChildNodesNotifyObserverSlimmer(FORM_ELEMENT_VALUE_CHANGE, () => {
      this.isTouched = true;
    });
    this._invalidFieldPathMap = new ImmutableMap(new Map(), (newMap) => {
      this._invalidFieldPathMap = newMap;
      this.updateIsValidFlag();
    });
    /**
     * The form field element pending async valid map
     */
    this._pendingValidFieldPathMap = new ImmutableMap(new Map(), (newMap) => {
      this._pendingValidFieldPathMap = newMap;
      this.updateIsValidFlag();
    });
    /**
     * If the element is valid, remove it from the set of
     *    invalid elements. If the element is invalid,
     *    add it to the set of invalid elements.
     *
     * @param {NotifyObserverPayload<boolean>} payload - CustomEvent<boolean> the vegaValidate subject notify from child elements
     */
    this.updateInternalValidStatus = (payload) => {
      if (payload.host !== this.host) {
        const target = payload.host;
        const isElementValid = payload.detail;
        if (FormFieldControllerSlimmer.isValidFormField(target) &&
          this.shouldVisitElement(target, false, false)) {
          if (isElementValid) {
            this.invalidFieldPathMap.delete(target);
          }
          else {
            this.invalidFieldPathMap.set(target, target.dataset['vegaForm']);
          }
        }
      }
    };
  }
  /**
   * Component lifecycle - [connectedCallback]
   */
  connectedCallback() {
    this.initFormFieldDisconnectedObserver();
    this.registerFormFieldPendingValid();
  }
  /**
   * When the vegaForm is deleted, it is necessary to notify the parent's vegaForm to update its isValid status.
   */
  disconnectedCallback() {
    ChangeManager.notify(FORM_FIELD_ELEMENT_DISCONNECTED, { host: this.host });
    ChangeManager.unregister(FORM_FIELD_ELEMENT_DISCONNECTED, this.formFieldDisconnectedObserver);
    ChangeManager.unregister(FORM_FIELD_PENDING_VALID_CHANGE, this.formFieldPendingValidObserver);
  }
  /**
   * @inheritDoc
   */
  async getValue(option) {
    const result = {};
    await this.navigateVegaFormChildren(async (element, isForm) => {
      const formFieldName = this.getFormFieldName(element);
      if (isForm) {
        result[formFieldName] = await element.getValue(option);
      }
      else {
        result[formFieldName] = this.getFormFieldValue(element);
      }
    }, option);
    return (isObjectArrayLike(result) ? Object.values(result) : result);
  }
  /**
   * @inheritDoc
   */
  async setValue(value, option) {
    if (value) {
      await this.navigateVegaFormChildren(async (element, isForm) => {
        const formFieldName = this.getFormFieldName(element);
        if (isForm) {
          await element.setValue(value[formFieldName], option);
        }
        else {
          await element.componentOnReady(); // Link issue https://gethired.atlassian.net/browse/GHUI-385
          if (value[formFieldName] !== undefined) {
            // If element have setValue method, we will directly call this method, otherwise we set the value to value property.
            typeof element['setValue'] === 'function'
              ? await element['setValue'](value[formFieldName])
              : (element['value'] = value[formFieldName]);
          }
        }
      }, option);
      this.isTouched = false;
    }
  }
  /**
   * @inheritDoc
   */
  async valid(showError = true) {
    const result = { isValid: true, invalidFields: [] };
    await this.navigateVegaFormChildren(async (element, isForm) => {
      if (typeof element['valid'] === 'function' && isForm) {
        const nestedFormValidationResult = await element.valid(showError);
        if (!nestedFormValidationResult.isValid) {
          result.isValid = false;
          result.invalidFields.push(...nestedFormValidationResult.invalidFields);
          result.invalidFieldsErrorMessages = Object.assign(Object.assign({}, result.invalidFieldsErrorMessages), nestedFormValidationResult.invalidFieldsErrorMessages);
        }
      }
      else {
        const { isValid, message } = await this.handleChildFormFieldElementValid(element, showError);
        if (!isValid) {
          result.isValid = false;
          result.invalidFields.push(this.getFormFieldName(element));
          if (!result.invalidFieldsErrorMessages) {
            result.invalidFieldsErrorMessages = {};
          }
          result.invalidFieldsErrorMessages[this.getFormFieldName(element)] = message;
        }
      }
    });
    return result;
  }
  /**
   * The getter for the {@link _invalidFieldPathMap}
   *
   * @returns {ImmutableMap<HTMLElement, string>} The map of invalid field elements and their paths
   */
  get invalidFieldPathMap() {
    return this._invalidFieldPathMap;
  }
  /**
   * The getter for the {@link _pendingValidFieldPathMap}
   *
   * @returns {ImmutableMap<HTMLElement, string>} The map of pending valid field elements and their paths
   */
  get pendingValidFieldPathMap() {
    return this._pendingValidFieldPathMap;
  }
  /**
   * Set the isTouched property of the form.
   *
   * @param {boolean} isTouched - The new value for isTouched.
   */
  setIsTouched(isTouched) {
    this.isTouched = isTouched;
  }
  /**
   * It returns the value of data-vega-form that the given element is associated with.
   *
   * @param {HTMLElement} element - The element that is being validated.
   * @returns {string} The name of the field.
   */
  getFormFieldName(element) {
    return element.dataset['vegaForm'];
  }
  /**
   * Check if the given element is part of the current form.
   *
   * @param {HTMLElement} element - The element to check.
   * @returns {boolean} True if the element is part of the current form, false otherwise.
   */
  isElementInCurrentForm(element) {
    return this.invalidFieldPathMap.has(element) || isParent(element, this.host);
  }
  /**
   * Navigate the immediate child node under current vega form element with the following strategy
   *  - if a matched vega field element found, will pass it to the visitor method passed in
   *  - if a non-matched node found, it will call itself recursively and pass the node as the second parameter
   * Once the navigation is finished, it will wait for all the pending visitor/self-invocation promise finished before return
   *
   * @param  {(element: HTMLElement, isForm: boolean) => Promise<void>} visitor async callback method that will be invoked if a matched vega field element found
   * @param {FormControlOption} [option] form control option to skip disabled/invisible components
   * @param {HTMLElement} [startElementNode=this.host] the element to start the navigation, default to current vega form host
   * @returns {Promise<void>} return void wrapped with promise to notify the navigation is done
   */
  async navigateVegaFormChildren(visitor, option, startElementNode = this.host) {
    const promiseQueue = [];
    Array.from(startElementNode.children).forEach((child) => {
      if (FormFieldControllerSlimmer.isValidFormField(child)) {
        if (this.shouldVisitElement(child, option === null || option === void 0 ? void 0 : option.skipDisabled, option === null || option === void 0 ? void 0 : option.skipInvisible)) {
          promiseQueue.push(visitor(child, child.tagName === 'VEGA-FORM'));
        }
      }
      else {
        promiseQueue.push(this.navigateVegaFormChildren(visitor, option, child));
      }
    });
    await Promise.all(promiseQueue);
  }
  /**
   * Update the {@link isValid} property value based on the size of {@link invalidFieldPathMap}
   * If the number of invalid elements is zero, then the form is valid.
   */
  updateIsValidFlag() {
    this.isValid = this.pendingValidFieldPathMap.size > 0 ? null : this.invalidFieldPathMap.size === 0;
  }
  /**
   * Registers an observer with the ChangeManager that will be notified when a form field's pending validation state changes.
   */
  registerFormFieldPendingValid() {
    this.formFieldPendingValidObserver = new Observer(this.canAcceptFormFieldPendingValid.bind(this), this.handleFieldPendingValid.bind(this));
    ChangeManager.register(FORM_FIELD_PENDING_VALID_CHANGE, this.formFieldPendingValidObserver);
  }
  /**
   * Can accept handler for FORM_FIELD_PENDING_VALID_CHANGE.
   *
   * @param {PendingValidPayload} payload - The payload containing the host element that triggered the pending valid change event.
   * @returns {boolean} True if the host element is part of the current form, false otherwise.
   */
  canAcceptFormFieldPendingValid(payload) {
    return this.isElementInCurrentForm(payload.host);
  }
  /**
   * Handles the pending valid state change for a form field.
   *
   * @param {PendingValidPayload} payload - The payload containing the host element and its pending valid state.
   */
  handleFieldPendingValid(payload) {
    if (typeof payload.detail === 'boolean') {
      this.pendingValidFieldPathMap.delete(payload.host);
    }
    else {
      this.pendingValidFieldPathMap.set(payload.host, payload.host.dataset['vegaForm']);
    }
  }
  /**
   * This function registers an observer with the ChangeManager that will be notified when an element
   * delete operation is triggered
   */
  initFormFieldDisconnectedObserver() {
    this.formFieldDisconnectedObserver = new Observer(this.canAcceptFormFieldDisconnectedOperation.bind(this), this.updateInvalidFieldPath.bind(this));
    ChangeManager.register(FORM_FIELD_ELEMENT_DISCONNECTED, this.formFieldDisconnectedObserver);
  }
  /**
   * If the deleted element is a child or grandchild of current vegaForm, the isValid of all these vegaForms will be updated.
   *
   * @param {DisconnectedElementOperationPayload} payload -  the delete element and vegaForm array.
   * @returns {boolean} A boolean value.
   */
  canAcceptFormFieldDisconnectedOperation(payload) {
    return this.invalidFieldPathMap.has(payload.host);
  }
  /**
   * Get the fieldName of the deleted element in the current vegaForm, delete it from the invalidFieldPathSet array, and update isValid at the same time.
   *
   * @param {DisconnectedElementOperationPayload} payload - the delete element and vegaForm array.
   */
  async updateInvalidFieldPath(payload) {
    this.invalidFieldPathMap.delete(payload.host);
    this.pendingValidFieldPathMap.delete(payload.host);
  }
  /**
   * Return true if the HTMLElement has data-vega-form property, and it's not disabled and not invisible if these two properties have to be checked.
   *
   * @param {HTMLElement} element - The HTMLElement to check.
   * @param {boolean} skipDisabled - If true, then disabled form fields will be skipped.
   * @param {boolean} skipInvisible - If true, only visible elements will be visited.
   * @returns {boolean} a boolean value.
   */
  shouldVisitElement(element, skipDisabled, skipInvisible) {
    return (this.getFormFieldName(element) &&
      (!skipDisabled || !element['disabled']) &&
      (!skipInvisible || isElementVisible(element)));
  }
  /**
   * It retrieves the value of a form field element.
   *
   * @param {HTMLElement} element - The form field element.
   * @returns {unknown} The value of the form field element.
   */
  getFormFieldValue(element) {
    return typeof element['value'] === 'undefined'
      ? // use element.getAttribute since the value property is not available in unit testing
        element.getAttribute('value')
      : element['value'];
  }
  /**
   * It handles the validation of a child form field element.
   *
   * @param {HTMLElement} element - The form field element to validate.
   * @param {boolean | 'rule'} showError - Whether to show error messages.
   * @returns {Promise<EvaluateResultBase>} The validation result.
   */
  async handleChildFormFieldElementValid(element, showError) {
    const subject = domNodeSubjectFactory.getSubject(element, VegaInternalValid);
    const { isValid, message } = await new Promise((resolve) => {
      //This setTimeout method here to avoid the dom node subject observer not existing and blocks the promise status
      const timerId = setTimeout(() => {
        resolve({ isValid: true, message: '' });
      }, 2000);
      ChangeManager.notify(subject, {
        host: element,
        message: showError,
        promiseResolve: resolve,
        timerId,
      });
    });
    return { isValid, message };
  }
}
__decorate([
  MapToComponentField()
], VegaFormFieldController.prototype, "host", void 0);
__decorate([
  MapToComponentField({ writable: true })
], VegaFormFieldController.prototype, "isValid", void 0);
__decorate([
  MapToComponentField({ writable: true })
], VegaFormFieldController.prototype, "isTouched", void 0);
__decorate([
  InjectVegaSlimmer()
], VegaFormFieldController.prototype, "childNotifyObserver", void 0);
__decorate([
  InjectVegaSlimmer()
], VegaFormFieldController.prototype, "childNodeValueChangeObserver", void 0);
__decorate([
  MapToComponentMethod('connectedCallback')
], VegaFormFieldController.prototype, "connectedCallback", null);
__decorate([
  MapToComponentMethod('disconnectedCallback')
], VegaFormFieldController.prototype, "disconnectedCallback", null);
__decorate([
  MapToComponentMethod('getValue')
], VegaFormFieldController.prototype, "getValue", null);
__decorate([
  MapToComponentMethod('setValue')
], VegaFormFieldController.prototype, "setValue", null);
__decorate([
  MapToComponentMethod('valid')
], VegaFormFieldController.prototype, "valid", null);
