text: /**
   * @license
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  (function() {
    'use strict';
  
    const MAX_AUTOCOMPLETE_RESULTS = 20;
  
    const RANGE_NAMES = [
      'QUERY LIMIT',
      'BATCH CHANGES LIMIT',
    ];
  
    /**
     * Fired when the permission has been modified or removed.
     *
     * @event access-modified
     */
  
    /**
     * Fired when a permission that was previously added was removed.
     *
     * @event added-permission-removed
     */
  
    Polymer({
      is: 'gr-permission',
  
      properties: {
        labels: Object,
        name: String,
        /** @type {?} */
        permission: {
          type: Object,
          observer: '_sortPermission',
          notify: true,
        },
        groups: Object,
        section: String,
        editing: {
          type: Boolean,
          value: false,
          observer: '_handleEditingChanged',
        },
        _label: {
          type: Object,
          computed: '_computeLabel(permission, labels)',
        },
        _groupFilter: String,
        _query: {
          type: Function,
          value() {
            return this._getGroupSuggestions.bind(this);
          },
        },
        _rules: Array,
        _groupsWithRules: Object,
        _deleted: {
          type: Boolean,
          value: false,
        },
        _originalExclusiveValue: Boolean,
      },
  
      behaviors: [
        Gerrit.AccessBehavior,
      ],
  
      observers: [
        '_handleRulesChanged(_rules.splices)',
      ],
  
      listeners: {
        'access-saved': '_handleAccessSaved',
      },
  
      ready() {
        this._setupValues();
      },
  
      _setupValues() {
        if (!this.permission) { return; }
        this._originalExclusiveValue = !!this.permission.value.exclusive;
        Polymer.dom.flush();
      },
  
      _handleAccessSaved() {
        // Set a new 'original' value to keep track of after the value has been
        // saved.
        this._setupValues();
      },
  
      _permissionIsOwnerOrGlobal(permissionId, section) {
        return permissionId === 'owner' || section === 'GLOBAL_CAPABILITIES';
      },
  
      _handleEditingChanged(editing, editingOld) {
        // Ignore when editing gets set initially.
        if (!editingOld) { return; }
        // Restore original values if no longer editing.
        if (!editing) {
          this._deleted = false;
          delete this.permission.value.deleted;
          this._groupFilter = '';
          this._rules = this._rules.filter(rule => !rule.value.added);
          for (const key of Object.keys(this.permission.value.rules)) {
            if (this.permission.value.rules[key].added) {
              delete this.permission.value.rules[key];
            }
          }
  
          // Restore exclusive bit to original.
          this.set(['permission', 'value', 'exclusive'],
              this._originalExclusiveValue);
        }
      },
  
      _handleAddedRuleRemoved(e) {
        const index = e.model.index;
        this._rules = this._rules.slice(0, index)
            .concat(this._rules.slice(index + 1, this._rules.length));
      },
  
      _handleValueChange() {
        this.permission.value.modified = true;
        // Allows overall access page to know a change has been made.
        this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
      },
  
      _handleRemovePermission() {
        if (this.permission.value.added) {
          this.dispatchEvent(new CustomEvent('added-permission-removed',
              {bubbles: true}));
        }
        this._deleted = true;
        this.permission.value.deleted = true;
        this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
      },
  
      _handleRulesChanged(changeRecord) {
        // Update the groups to exclude in the autocomplete.
        this._groupsWithRules = this._computeGroupsWithRules(this._rules);
      },
  
      _sortPermission(permission) {
        this._rules = this.toSortedArray(permission.value.rules);
      },
  
      _computeSectionClass(editing, deleted) {
        const classList = [];
        if (editing) {
          classList.push('editing');
        }
        if (deleted) {
          classList.push('deleted');
        }
        return classList.join(' ');
      },
  
      _handleUndoRemove() {
        this._deleted = false;
        delete this.permission.value.deleted;
      },
  
      _computeLabel(permission, labels) {
        if (!permission.value.label) { return; }
  
        const labelName = permission.value.label;
  
        // It is possible to have a label name that is not included in the
        // 'labels' object. In this case, treat it like anything else.
        if (!labels[labelName]) { return; }
        const label = {
          name: labelName,
          values: this._computeLabelValues(labels[labelName].values),
        };
        return label;
      },
  
      _computeLabelValues(values) {
        const valuesArr = [];
        const keys = Object.keys(values).sort((a, b) => {
          return parseInt(a, 10) - parseInt(b, 10);
        });
  
        for (const key of keys) {
          let text = values[key];
          if (!text) { text = ''; }
          // The value from the server being used to choose which item is
          // selected is in integer form, so this must be converted.
          valuesArr.push({value: parseInt(key, 10), text});
        }
        return valuesArr;
      },
  
      /**
       * @param {!Array} rules
       * @return {!Object} Object with groups with rues as keys, and true as
       *    value.
       */
      _computeGroupsWithRules(rules) {
        const groups = {};
        for (const rule of rules) {
          groups[rule.id] = true;
        }
        return groups;
      },
  
      _computeGroupName(groups, groupId) {
        return groups && groups[groupId] && groups[groupId].name ?
          groups[groupId].name : groupId;
      },
  
      _getGroupSuggestions() {
        return this.$.restAPI.getSuggestedGroups(
            this._groupFilter,
            MAX_AUTOCOMPLETE_RESULTS)
            .then(response => {
              const groups = [];
              for (const key in response) {
                if (!response.hasOwnProperty(key)) { continue; }
                groups.push({
                  name: key,
                  value: response[key],
                });
              }
              // Does not return groups in which we already have rules for.
              return groups.filter(group => {
                return !this._groupsWithRules[group.value.id];
              });
            });
      },
  
      /**
       * Handles adding a skeleton item to the dom-repeat.
       * gr-rule-editor handles setting the default values.
       */
      _handleAddRuleItem(e) {
        // The group id is encoded, but have to decode in order for the access
        // API to work as expected.
        const groupId = decodeURIComponent(e.detail.value.id).replace(/\+/g, ' ');
        // We cannot use "this.set(...)" here, because groupId may contain dots,
        // and dots in property path names are totally unsupported by Polymer.
        // Apparently Polymer picks up this change anyway, otherwise we should
        // have looked at using MutableData:
        // https://polymer-library.polymer-project.org/2.0/docs/devguide/data-system#mutable-data
        this.permission.value.rules[groupId] = {};
  
        // Purposely don't recompute sorted array so that the newly added rule
        // is the last item of the array.
        this.push('_rules', {
          id: groupId,
        });
  
        // Add the new group name to the groups object so the name renders
        // correctly.
        if (this.groups && !this.groups[groupId]) {
          this.groups[groupId] = {name: this.$.groupAutocomplete.text};
        }
  
        // Wait for new rule to get value populated via gr-rule-editor, and then
        // add to permission values as well, so that the change gets propogated
        // back to the section. Since the rule is inside a dom-repeat, a flush
        // is needed.
        Polymer.dom.flush();
        const value = this._rules[this._rules.length - 1].value;
        value.added = true;
        // See comment above for why we cannot use "this.set(...)" here.
        this.permission.value.rules[groupId] = value;
        this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
      },
  
      _computeHasRange(name) {
        if (!name) { return false; }
  
        return RANGE_NAMES.includes(name.toUpperCase());
      },
    });
  })();
  