/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Implements a service used by DOM content to request Form Autofill.
 */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

/**
 * Handles profile autofill for a DOM Form element.
 * @param {HTMLFormElement} form Form that need to be auto filled
 */
function FormAutofillHandler(form) {
  this.form = form;
  this.fieldDetails = [];
}

FormAutofillHandler.prototype = {
  /**
   * DOM Form element to which this object is attached.
   */
  form: null,

  /**
   * Array of collected data about relevant form fields.  Each item is an object
   * storing the identifying details of the field and a reference to the
   * originally associated element from the form.
   *
   * The "section", "addressType", "contactType", and "fieldName" values are
   * used to identify the exact field when the serializable data is received
   * from the backend.  There cannot be multiple fields which have
   * the same exact combination of these values.
   *
   * A direct reference to the associated element cannot be sent to the user
   * interface because processing may be done in the parent process.
   */
  fieldDetails: null,

  /**
   * Returns information from the form about fields that can be autofilled, and
   * populates the fieldDetails array on this object accordingly.
   *
   * @returns {Array<Object>} Serializable data structure that can be sent to the user
   *          interface, or null if the operation failed because the constraints
   *          on the allowed fields were not honored.
   */
  collectFormFields() {
    let autofillData = [];

    for (let element of this.form.elements) {
      // Query the interface and exclude elements that cannot be autocompleted.
      if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
        continue;
      }

      // Exclude elements to which no autocomplete field has been assigned.
      let info = element.getAutocompleteInfo();
      if (!info.fieldName || ["on", "off"].includes(info.fieldName)) {
        continue;
      }

      // Store the association between the field metadata and the element.
      if (this.fieldDetails.some(f => f.section == info.section &&
                                      f.addressType == info.addressType &&
                                      f.contactType == info.contactType &&
                                      f.fieldName == info.fieldName)) {
        // A field with the same identifier already exists.
        return null;
      }

      let inputFormat = {
        section: info.section,
        addressType: info.addressType,
        contactType: info.contactType,
        fieldName: info.fieldName,
      };
      // Clone the inputFormat for caching the fields and elements together
      let formatWithElement = Object.assign({}, inputFormat);

      inputFormat.index = autofillData.length;
      autofillData.push(inputFormat);

      formatWithElement.element = element;
      this.fieldDetails.push(formatWithElement);
    }

    return autofillData;
  },

  /**
   * Processes form fields that can be autofilled, and populates them with the
   * data provided by backend.
   *
   * @param {Array<Object>} autofillResult
   *        Data returned by the user interface.
   *        [{
   *          section: Value originally provided to the user interface.
   *          addressType: Value originally provided to the user interface.
   *          contactType: Value originally provided to the user interface.
   *          fieldName: Value originally provided to the user interface.
   *          value: String with which the field should be updated.
   *          index: Index to match the input in fieldDetails
   *        }],
   *        }
   */
  autofillFormFields(autofillResult) {
    for (let field of autofillResult) {
      // Get the field details, if it was processed by the user interface.
      let fieldDetail = this.fieldDetails[field.index];

      // Avoid the invalid value set
      if (!fieldDetail || !field.value) {
        continue;
      }

      let info = fieldDetail.element.getAutocompleteInfo();
      if (field.section != info.section ||
          field.addressType != info.addressType ||
          field.contactType != info.contactType ||
          field.fieldName != info.fieldName) {
        Cu.reportError("Autocomplete tokens mismatched");
        continue;
      }

      fieldDetail.element.setUserInput(field.value);
    }
  },
};

this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];