text: /**
   * @license
   * Copyright (C) 2016 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 HASHTAG_ADD_MESSAGE = 'Add Hashtag';
  
    const SubmitTypeLabel = {
      FAST_FORWARD_ONLY: 'Fast Forward Only',
      MERGE_IF_NECESSARY: 'Merge if Necessary',
      REBASE_IF_NECESSARY: 'Rebase if Necessary',
      MERGE_ALWAYS: 'Always Merge',
      REBASE_ALWAYS: 'Rebase Always',
      CHERRY_PICK: 'Cherry Pick',
    };
  
    const NOT_CURRENT_MESSAGE = 'Not current - rebase possible';
  
    /**
     * @enum {string}
     */
    const CertificateStatus = {
      /**
       * This certificate status is bad.
       */
      BAD: 'BAD',
      /**
       * This certificate status is OK.
       */
      OK: 'OK',
      /**
       * This certificate status is TRUSTED.
       */
      TRUSTED: 'TRUSTED',
    };
  
    /**
     * @appliesMixin Gerrit.RESTClientMixin
     * @extends Polymer.Element
     */
    class GrChangeMetadata extends Polymer.mixinBehaviors( [
      Gerrit.RESTClientBehavior,
    ], Polymer.GestureEventListeners(
        Polymer.LegacyElementMixin(
            Polymer.Element))) {
      static get is() { return 'gr-change-metadata'; }
      /**
       * Fired when the change topic is changed.
       *
       * @event topic-changed
       */
  
      static get properties() {
        return {
        /** @type {?} */
          change: Object,
          labels: {
            type: Object,
            notify: true,
          },
          account: Object,
          /** @type {?} */
          revision: Object,
          commitInfo: Object,
          _mutable: {
            type: Boolean,
            computed: '_computeIsMutable(account)',
          },
          /** @type {?} */
          serverConfig: Object,
          parentIsCurrent: Boolean,
          _notCurrentMessage: {
            type: String,
            value: NOT_CURRENT_MESSAGE,
            readOnly: true,
          },
          _topicReadOnly: {
            type: Boolean,
            computed: '_computeTopicReadOnly(_mutable, change)',
          },
          _hashtagReadOnly: {
            type: Boolean,
            computed: '_computeHashtagReadOnly(_mutable, change)',
          },
          /**
           * @type {Gerrit.PushCertificateValidation}
           */
          _pushCertificateValidation: {
            type: Object,
            computed: '_computePushCertificateValidation(serverConfig, change)',
          },
          _showRequirements: {
            type: Boolean,
            computed: '_computeShowRequirements(change)',
          },
  
          _assignee: Array,
          _isWip: {
            type: Boolean,
            computed: '_computeIsWip(change)',
          },
          _newHashtag: String,
  
          _settingTopic: {
            type: Boolean,
            value: false,
          },
  
          _currentParents: {
            type: Array,
            computed: '_computeParents(change)',
          },
  
          /** @type {?} */
          _CHANGE_ROLE: {
            type: Object,
            readOnly: true,
            value: {
              OWNER: 'owner',
              UPLOADER: 'uploader',
              AUTHOR: 'author',
              COMMITTER: 'committer',
            },
          },
        };
      }
  
      static get observers() {
        return [
          '_changeChanged(change)',
          '_labelsChanged(change.labels)',
          '_assigneeChanged(_assignee.*)',
        ];
      }
  
      _labelsChanged(labels) {
        this.labels = Object.assign({}, labels) || null;
      }
  
      _changeChanged(change) {
        this._assignee = change.assignee ? [change.assignee] : [];
      }
  
      _assigneeChanged(assigneeRecord) {
        if (!this.change) { return; }
        const assignee = assigneeRecord.base;
        if (assignee.length) {
          const acct = assignee[0];
          if (this.change.assignee &&
              acct._account_id === this.change.assignee._account_id) { return; }
          this.set(['change', 'assignee'], acct);
          this.$.restAPI.setAssignee(this.change._number, acct._account_id);
        } else {
          if (!this.change.assignee) { return; }
          this.set(['change', 'assignee'], undefined);
          this.$.restAPI.deleteAssignee(this.change._number);
        }
      }
  
      _computeHideStrategy(change) {
        return !this.changeIsOpen(change);
      }
  
      /**
       * @param {Object} commitInfo
       * @return {?Array} If array is empty, returns null instead so
       * an existential check can be used to hide or show the webLinks
       * section.
       */
      _computeWebLinks(commitInfo, serverConfig) {
        if (!commitInfo) { return null; }
        const weblinks = Gerrit.Nav.getChangeWeblinks(
            this.change ? this.change.repo : '',
            commitInfo.commit,
            {
              weblinks: commitInfo.web_links,
              config: serverConfig,
            });
        return weblinks.length ? weblinks : null;
      }
  
      _computeStrategy(change) {
        return SubmitTypeLabel[change.submit_type];
      }
  
      _computeLabelNames(labels) {
        return Object.keys(labels).sort();
      }
  
      _handleTopicChanged(e, topic) {
        const lastTopic = this.change.topic;
        if (!topic.length) { topic = null; }
        this._settingTopic = true;
        this.$.restAPI.setChangeTopic(this.change._number, topic)
            .then(newTopic => {
              this._settingTopic = false;
              this.set(['change', 'topic'], newTopic);
              if (newTopic !== lastTopic) {
                this.dispatchEvent(new CustomEvent(
                    'topic-changed', {bubbles: true, composed: true}));
              }
            });
      }
  
      _showAddTopic(changeRecord, settingTopic) {
        const hasTopic = !!changeRecord &&
            !!changeRecord.base && !!changeRecord.base.topic;
        return !hasTopic && !settingTopic;
      }
  
      _showTopicChip(changeRecord, settingTopic) {
        const hasTopic = !!changeRecord &&
            !!changeRecord.base && !!changeRecord.base.topic;
        return hasTopic && !settingTopic;
      }
  
      _showCherryPickOf(changeRecord) {
        const hasCherryPickOf = !!changeRecord &&
            !!changeRecord.base && !!changeRecord.base.cherry_pick_of_change &&
            !!changeRecord.base.cherry_pick_of_patch_set;
        return hasCherryPickOf;
      }
  
      _handleHashtagChanged(e) {
        const lastHashtag = this.change.hashtag;
        if (!this._newHashtag.length) { return; }
        const newHashtag = this._newHashtag;
        this._newHashtag = '';
        this.$.restAPI.setChangeHashtag(
            this.change._number, {add: [newHashtag]}).then(newHashtag => {
          this.set(['change', 'hashtags'], newHashtag);
          if (newHashtag !== lastHashtag) {
            this.dispatchEvent(
                new CustomEvent('hashtag-changed', {
                  bubbles: true, composed: true}));
          }
        });
      }
  
      _computeTopicReadOnly(mutable, change) {
        return !mutable ||
            !change ||
            !change.actions ||
            !change.actions.topic ||
            !change.actions.topic.enabled;
      }
  
      _computeHashtagReadOnly(mutable, change) {
        return !mutable ||
            !change ||
            !change.actions ||
            !change.actions.hashtags ||
            !change.actions.hashtags.enabled;
      }
  
      _computeAssigneeReadOnly(mutable, change) {
        return !mutable ||
            !change ||
            !change.actions ||
            !change.actions.assignee ||
            !change.actions.assignee.enabled;
      }
  
      _computeTopicPlaceholder(_topicReadOnly) {
        // Action items in Material Design are uppercase -- placeholder label text
        // is sentence case.
        return _topicReadOnly ? 'No topic' : 'ADD TOPIC';
      }
  
      _computeHashtagPlaceholder(_hashtagReadOnly) {
        return _hashtagReadOnly ? '' : HASHTAG_ADD_MESSAGE;
      }
  
      _computeShowRequirements(change) {
        if (change.status !== this.ChangeStatus.NEW) {
          // TODO(maximeg) change this to display the stored
          // requirements, once it is implemented server-side.
          return false;
        }
        const hasRequirements = !!change.requirements &&
            Object.keys(change.requirements).length > 0;
        const hasLabels = !!change.labels &&
            Object.keys(change.labels).length > 0;
        return hasRequirements || hasLabels || !!change.work_in_progress;
      }
  
      /**
       * @return {?Gerrit.PushCertificateValidation} object representing data for
       *     the push validation.
       */
      _computePushCertificateValidation(serverConfig, change) {
        if (!change || !serverConfig || !serverConfig.receive ||
            !serverConfig.receive.enable_signed_push) {
          return null;
        }
        const rev = change.revisions[change.current_revision];
        if (!rev.push_certificate || !rev.push_certificate.key) {
          return {
            class: 'help',
            icon: 'gr-icons:help',
            message: 'This patch set was created without a push certificate',
          };
        }
  
        const key = rev.push_certificate.key;
        switch (key.status) {
          case CertificateStatus.BAD:
            return {
              class: 'invalid',
              icon: 'gr-icons:close',
              message: this._problems('Push certificate is invalid', key),
            };
          case CertificateStatus.OK:
            return {
              class: 'notTrusted',
              icon: 'gr-icons:info',
              message: this._problems(
                  'Push certificate is valid, but key is not trusted', key),
            };
          case CertificateStatus.TRUSTED:
            return {
              class: 'trusted',
              icon: 'gr-icons:check',
              message: this._problems(
                  'Push certificate is valid and key is trusted', key),
            };
          default:
            throw new Error(`unknown certificate status: ${key.status}`);
        }
      }
  
      _problems(msg, key) {
        if (!key || !key.problems || key.problems.length === 0) {
          return msg;
        }
  
        return [msg + ':'].concat(key.problems).join('\n');
      }
  
      _computeShowRepoBranchTogether(repo, branch) {
        return !!repo && !!branch && repo.length + branch.length < 40;
      }
  
      _computeProjectUrl(project) {
        return Gerrit.Nav.getUrlForProjectChanges(project);
      }
  
      _computeBranchUrl(project, branch) {
        if (!this.change || !this.change.status) return '';
        return Gerrit.Nav.getUrlForBranch(branch, project,
            this.change.status == this.ChangeStatus.NEW ? 'open' :
              this.change.status.toLowerCase());
      }
  
      _computeCherryPickOfUrl(change, patchset, project) {
        return Gerrit.Nav.getUrlForChangeById(change, project, patchset);
      }
  
      _computeTopicUrl(topic) {
        return Gerrit.Nav.getUrlForTopic(topic);
      }
  
      _computeHashtagUrl(hashtag) {
        return Gerrit.Nav.getUrlForHashtag(hashtag);
      }
  
      _handleTopicRemoved(e) {
        const target = Polymer.dom(e).rootTarget;
        target.disabled = true;
        this.$.restAPI.setChangeTopic(this.change._number, null)
            .then(() => {
              target.disabled = false;
              this.set(['change', 'topic'], '');
              this.dispatchEvent(
                  new CustomEvent('topic-changed',
                      {bubbles: true, composed: true}));
            })
            .catch(err => {
              target.disabled = false;
              return;
            });
      }
  
      _handleHashtagRemoved(e) {
        e.preventDefault();
        const target = Polymer.dom(e).rootTarget;
        target.disabled = true;
        this.$.restAPI.setChangeHashtag(this.change._number,
            {remove: [target.text]})
            .then(newHashtag => {
              target.disabled = false;
              this.set(['change', 'hashtags'], newHashtag);
            })
            .catch(err => {
              target.disabled = false;
              return;
            });
      }
  
      _computeIsWip(change) {
        return !!change.work_in_progress;
      }
  
      _computeShowRoleClass(change, role) {
        return this._getNonOwnerRole(change, role) ? '' : 'hideDisplay';
      }
  
      /**
       * Get the user with the specified role on the change. Returns null if the
       * user with that role is the same as the owner.
       *
       * @param {!Object} change
       * @param {string} role One of the values from _CHANGE_ROLE
       * @return {Object|null} either an accound or null.
       */
      _getNonOwnerRole(change, role) {
        if (!change || !change.current_revision ||
            !change.revisions[change.current_revision]) {
          return null;
        }
  
        const rev = change.revisions[change.current_revision];
        if (!rev) { return null; }
  
        if (role === this._CHANGE_ROLE.UPLOADER &&
            rev.uploader &&
            change.owner._account_id !== rev.uploader._account_id) {
          return rev.uploader;
        }
  
        if (role === this._CHANGE_ROLE.AUTHOR &&
            rev.commit && rev.commit.author &&
            change.owner.email !== rev.commit.author.email) {
          return rev.commit.author;
        }
  
        if (role === this._CHANGE_ROLE.COMMITTER &&
            rev.commit && rev.commit.committer &&
            change.owner.email !== rev.commit.committer.email) {
          return rev.commit.committer;
        }
  
        return null;
      }
  
      _computeParents(change) {
        if (!change || !change.current_revision ||
            !change.revisions[change.current_revision] ||
            !change.revisions[change.current_revision].commit) {
          return undefined;
        }
        return change.revisions[change.current_revision].commit.parents;
      }
  
      _computeParentsLabel(parents) {
        return parents && parents.length > 1 ? 'Parents' : 'Parent';
      }
  
      _computeParentListClass(parents, parentIsCurrent) {
        // Undefined check for polymer 2
        if (parents === undefined || parentIsCurrent === undefined) {
          return '';
        }
  
        return [
          'parentList',
          parents && parents.length > 1 ? 'merge' : 'nonMerge',
          parentIsCurrent ? 'current' : 'notCurrent',
        ].join(' ');
      }
  
      _computeIsMutable(account) {
        return !!Object.keys(account).length;
      }
  
      editTopic() {
        if (this._topicReadOnly || this.change.topic) { return; }
        // Cannot use `this.$.ID` syntax because the element exists inside of a
        // dom-if.
        this.$$('.topicEditableLabel').open();
      }
  
      _getReviewerSuggestionsProvider(change) {
        const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI,
            change._number, Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY);
        provider.init();
        return provider;
      }
    }
  
    customElements.define(GrChangeMetadata.is, GrChangeMetadata);
  })();
  