/* 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/. */
import AddressForm from "./address-form.js";
import AddressOption from "../components/address-option.js";
import RichPicker from "./rich-picker.js";
import paymentRequest from "../paymentRequest.js";
import HandleEventMixin from "../mixins/HandleEventMixin.js";
/**
 * 
 * Container around add/edit links and  with
 *  listening to savedAddresses & tempAddresses.
 */
export default class AddressPicker extends HandleEventMixin(RichPicker) {
  static get pickerAttributes() {
    return [
      "address-fields",
      "break-after-nth-field",
      "data-field-separator",
    ];
  }
  static get observedAttributes() {
    return RichPicker.observedAttributes.concat(AddressPicker.pickerAttributes);
  }
  constructor() {
    super();
    this.dropdown.setAttribute("option-type", "address-option");
  }
  attributeChangedCallback(name, oldValue, newValue) {
    super.attributeChangedCallback(name, oldValue, newValue);
    // connectedCallback may add and adjust elements & values
    // so avoid calling render before the element is connected
    if (this.isConnected &&
        AddressPicker.pickerAttributes.includes(name) && oldValue !== newValue) {
      this.render(this.requestStore.getState());
    }
  }
  get fieldNames() {
    if (this.hasAttribute("address-fields")) {
      let names = this.getAttribute("address-fields").trim().split(/\s+/);
      if (names.length) {
        return names;
      }
    }
    return [
      // "address-level1", // TODO: bug 1481481 - not required for some countries e.g. DE
      "address-level2",
      "country",
      "name",
      "postal-code",
      "street-address",
    ];
  }
  /**
   * De-dupe and filter addresses for the given set of fields that will be visible
   *
   * @param {object} addresses
   * @param {array?} fieldNames - optional list of field names that be used when
   *                              de-duping and excluding entries
   * @returns {object} filtered copy of given addresses
   */
  filterAddresses(addresses, fieldNames = this.fieldNames) {
    let uniques = new Set();
    let result = {};
    for (let [guid, address] of Object.entries(addresses)) {
      let addressCopy = {};
      let isMatch = false;
      // exclude addresses that are missing all of the requested fields
      for (let name of fieldNames) {
        if (address[name]) {
          isMatch = true;
          addressCopy[name] = address[name];
        }
      }
      if (isMatch) {
        let key = JSON.stringify(addressCopy);
        // exclude duplicated addresses
        if (!uniques.has(key)) {
          uniques.add(key);
          result[guid] = address;
        }
      }
    }
    return result;
  }
  get options() {
    return this.dropdown.popupBox.options;
  }
  /**
   * @param {object} state - See `PaymentsStore.setState`
   * The value of the picker is retrieved from state store rather than the DOM
   * @returns {string} guid
   */
  getCurrentValue(state) {
    let [selectedKey, selectedLeaf] = this.selectedStateKey.split("|");
    let guid = state[selectedKey];
    if (selectedLeaf) {
      guid = guid[selectedLeaf];
    }
    return guid;
  }
  render(state) {
    let selectedAddressGUID = this.getCurrentValue(state) || "";
    let addresses = paymentRequest.getAddresses(state);
    let desiredOptions = [];
    let filteredAddresses = this.filterAddresses(addresses, this.fieldNames);
    for (let [guid, address] of Object.entries(filteredAddresses)) {
      let optionEl = this.dropdown.getOptionByValue(guid);
      if (!optionEl) {
        optionEl = document.createElement("option");
        optionEl.value = guid;
      }
      for (let key of AddressOption.recordAttributes) {
        let val = address[key];
        if (val) {
          optionEl.setAttribute(key, val);
        } else {
          optionEl.removeAttribute(key);
        }
      }
      optionEl.dataset.fieldSeparator = this.dataset.fieldSeparator;
      if (this.hasAttribute("address-fields")) {
        optionEl.setAttribute("address-fields", this.getAttribute("address-fields"));
      } else {
        optionEl.removeAttribute("address-fields");
      }
      if (this.hasAttribute("break-after-nth-field")) {
        optionEl.setAttribute("break-after-nth-field", this.getAttribute("break-after-nth-field"));
      } else {
        optionEl.removeAttribute("break-after-nth-field");
      }
      // fieldNames getter is not used here because it returns a default array with
      // attributes even when "address-fields" observed attribute is null.
      let addressFields = this.getAttribute("address-fields");
      optionEl.textContent = AddressOption.formatSingleLineLabel(address, addressFields);
      desiredOptions.push(optionEl);
    }
    this.dropdown.popupBox.textContent = "";
    if (this._allowEmptyOption) {
      let optionEl = document.createElement("option");
      optionEl.value = "";
      desiredOptions.unshift(optionEl);
    }
    for (let option of desiredOptions) {
      this.dropdown.popupBox.appendChild(option);
    }
    // Update selectedness after the options are updated
    this.dropdown.value = selectedAddressGUID;
    if (selectedAddressGUID && selectedAddressGUID !== this.dropdown.value) {
      throw new Error(`${this.selectedStateKey} option ${selectedAddressGUID} ` +
                      `does not exist in the address picker`);
    }
    super.render(state);
  }
  get selectedStateKey() {
    return this.getAttribute("selected-state-key");
  }
  errorForSelectedOption(state) {
    let superError = super.errorForSelectedOption(state);
    if (superError) {
      return superError;
    }
    if (!this.selectedOption) {
      return "";
    }
    let merchantFieldErrors = AddressForm.merchantFieldErrorsForForm(
          state, this.selectedStateKey.split("|"));
    // TODO: errors in priority order.
    return Object.values(merchantFieldErrors).find(msg => {
      return typeof(msg) == "string" && msg.length;
    }) || "";
  }
  onChange(event) {
    let [selectedKey, selectedLeaf] = this.selectedStateKey.split("|");
    if (!selectedKey) {
      return;
    }
    // selectedStateKey can be a '|' delimited string indicating a path into the state object
    // to update with the new value
    let newState = {};
    if (selectedLeaf) {
      let currentState = this.requestStore.getState();
      newState[selectedKey] = Object.assign({},
                                            currentState[selectedKey],
                                            { [selectedLeaf]: this.dropdown.value });
    } else {
      newState[selectedKey] = this.dropdown.value;
    }
    this.requestStore.setState(newState);
  }
  onClick({target}) {
    let pageId;
    let currentState = this.requestStore.getState();
    let nextState = {
      page: {},
    };
    switch (this.selectedStateKey) {
      case "selectedShippingAddress":
        pageId = "shipping-address-page";
        break;
      case "selectedPayerAddress":
        pageId = "payer-address-page";
        break;
      case "basic-card-page|billingAddressGUID":
        pageId = "billing-address-page";
        break;
      default: {
        throw new Error("onClick, un-matched selectedStateKey: " +
                        this.selectedStateKey);
      }
    }
    nextState.page.id = pageId;
    let addressFields = this.getAttribute("address-fields");
    nextState[pageId] = { addressFields };
    switch (target) {
      case this.addLink: {
        nextState[pageId].guid = null;
        break;
      }
      case this.editLink: {
        nextState[pageId].guid = this.getCurrentValue(currentState);
        break;
      }
      default: {
        throw new Error("Unexpected onClick");
      }
    }
    this.requestStore.setState(nextState);
  }
}
customElements.define("address-picker", AddressPicker);