'use strict';

(function () {

    /**
     * Base class for review request banners.
     *
     * This will render a banner based on the data provided by subclasses,
     * and handle actions and editing of text fields.
     */
    var BannerView = Backbone.View.extend({
        className: 'banner',
        title: '',
        subtitle: '',
        actions: [],
        showChangesField: true,
        describeText: '',
        fieldOptions: {},
        descriptionFieldID: 'change_description',
        descriptionFieldName: null,
        descriptionFieldHTML: '',
        descriptionFieldClasses: '',
        showSendEmail: false,
        DescriptionFieldViewType: RB.ReviewRequestFields.ChangeDescriptionFieldView,

        template: _.template('<h1><%- title %></h1>\n<% if (subtitle) { %>\n<p><%- subtitle %></p>\n<% } %>\n<span class="banner-actions">\n<% _.each(actions, function(action) { %>\n <input type="button" id="<%= action.id %>"\n        value="<%- action.label %>" />\n<% }); %>\n<% if (showSendEmail) { %>\n <label>\n  <input type="checkbox" class="send-email" checked />\n  <%- sendEmailText %>\n</label>\n<% } %>\n</span>\n<% if (showChangesField) { %>\n <p><label for="field_<%- descriptionFieldID %>"><%- describeText %></label></p>\n <pre id="field_<%- descriptionFieldID %>"\n      class="field field-text-area <%- descriptionFieldClasses %>"\n      data-field-id="field_<%- descriptionFieldID %>"\n      ><%= descriptionFieldHTML %></pre>\n<% } %>'),

        /**
         * Initialize the banner.
         *
         * Args:
         *     options (object):
         *         Options for the banner.
         *
         * Option Args:
         *     reviewRequestEditorView (RB.ReviewRequestEditorView):
         *         The review request editor.
         */
        initialize: function initialize(options) {
            this.reviewRequestEditorView = options.reviewRequestEditorView;
            this.reviewRequestEditor = this.reviewRequestEditorView.model;
            this.reviewRequest = this.reviewRequestEditor.get('reviewRequest');
            this.$buttons = null;
        },


        /**
         * Render the banner.
         *
         * If there's an existing banner on the page, from the generated
         * template, then this will make use of that template. Otherwise,
         * it will construct a new one.
         *
         * Returns:
         *     BannerView:
         *     This object, for chaining.
         */
        render: function render() {
            var _this = this;

            if (this.$el.children().length === 0) {
                this.$el.html(this.template({
                    title: this.title,
                    subtitle: this.subtitle,
                    actions: this.actions,
                    showChangesField: this.showChangesField,
                    describeText: this.describeText,
                    descriptionFieldID: this.descriptionFieldID,
                    descriptionFieldHTML: this.descriptionFieldHTML,
                    descriptionFieldClasses: this.descriptionFieldClasses,
                    showSendEmail: this.showSendEmail,
                    sendEmailText: gettext('Send E-Mail')
                }));
            }

            if (this.DescriptionFieldViewType) {
                this.field = new this.DescriptionFieldViewType({
                    el: this.$('#field_' + this.descriptionFieldID),
                    fieldID: this.descriptionFieldID,
                    model: this.reviewRequestEditor
                });

                this.reviewRequestEditorView.addFieldView(this.field);
            }

            this.$buttons = this.$('input');

            this.reviewRequestEditor.on('saving destroying', function () {
                return _this.$buttons.prop('disabled', true);
            });

            this.reviewRequestEditor.on('saved saveFailed destroyed', function () {
                return _this.$buttons.prop('disabled', false);
            });

            return this;
        }
    });

    /**
     * Base class for a banner representing a closed review request.
     *
     * This provides a button for reopening the review request. It's up
     * to subclasses to provide the other details.
     */
    var ClosedBannerView = BannerView.extend({
        descriptionFieldID: 'close_description',
        descriptionFieldName: 'closeDescription',
        DescriptionFieldViewType: RB.ReviewRequestFields.CloseDescriptionFieldView,

        actions: [{
            id: 'btn-review-request-reopen',
            label: gettext('Reopen for Review')
        }],

        closeType: undefined,

        events: {
            'click #btn-review-request-reopen': '_onReopenClicked'
        },

        /**
         * Render the banner.
         *
         * Returns:
         *     ClosedBannerView:
         *     This object, for chaining.
         */
        render: function render() {
            var descriptionFieldClasses = [];

            if (this.reviewRequestEditor.get('statusMutableByUser')) {
                descriptionFieldClasses.push('editable');
            }

            if (this.reviewRequest.get('closeDescriptionRichText')) {
                descriptionFieldClasses.push('rich-text');
            }

            this.descriptionFieldClasses = descriptionFieldClasses.join(' ');
            this.descriptionFieldHTML = this.reviewRequestEditor.get('closeDescriptionRenderedText');

            BannerView.prototype.render.apply(this, arguments);

            this.field.closeType = this.closeType;

            return this;
        },


        /**
         * Handle a click on "Reopen for Review".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onReopenClicked: function _onReopenClicked() {
            this.reviewRequest.reopen({
                error: function error(model, xhr) {
                    return alert(xhr.errorText);
                }
            });

            return false;
        }
    });

    /**
     * A banner representing a discarded review request.
     */
    var DiscardedBannerView = ClosedBannerView.extend({
        id: 'discard-banner',
        title: gettext('This change has been discarded.'),
        describeText: gettext("Describe the reason it's discarded (optional):"),
        closeType: RB.ReviewRequest.CLOSE_DISCARDED
    });

    /**
     * A banner representing a submitted review request.
     */
    var SubmittedBannerView = ClosedBannerView.extend({
        id: 'submitted-banner',
        title: gettext('This change has been marked as submitted.'),
        describeText: gettext('Describe the submission (optional):'),
        closeType: RB.ReviewRequest.CLOSE_SUBMITTED
    });

    /**
     * A banner representing a draft of a review request.
     *
     * Depending on the public state of the review request, this will
     * show different text and a different set of buttons.
     */
    var DraftBannerView = BannerView.extend({
        id: 'draft-banner',
        title: gettext('This review request is a draft.'),
        subtitle: gettext('Be sure to publish when finished.'),
        describeText: gettext('Describe your changes (optional):'),
        descriptionFieldID: 'change_description',
        descriptionFieldName: 'changeDescription',

        _newDraftTemplate: _.template('<div class="interdiff-link">\n <%- newDiffText %>\n <a href="<%- interdiffLink %>"><%- showChangesText %></a>\n</div>'),

        events: {
            'click #btn-draft-publish': '_onPublishDraftClicked',
            'click #btn-draft-discard': '_onDiscardDraftClicked',
            'click #btn-review-request-discard': '_onCloseDiscardedClicked'
        },

        /**
         * Initialize the banner.
         */
        initialize: function initialize() {
            BannerView.prototype.initialize.apply(this, arguments);

            if (this.reviewRequest.get('public')) {
                this.showSendEmail = this.reviewRequestEditor.get('showSendEmail');
                this.title = gettext('This review request is a draft.');
                this.actions = [{
                    id: 'btn-draft-publish',
                    label: gettext('Publish Changes')
                }, {
                    id: 'btn-draft-discard',
                    label: gettext('Discard Draft')
                }];
            } else {
                this.showChangesField = false;
                this.actions = [{
                    id: 'btn-draft-publish',
                    label: gettext('Publish')
                }, {
                    id: 'btn-review-request-discard',
                    label: gettext('Discard Review Request')
                }];
            }
        },


        /**
         * Handle a click on "Publish Changes".
         *
         * Begins publishing the review request. If there are any field editors
         * still open, they'll be saved first.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onPublishDraftClicked: function _onPublishDraftClicked() {
            var $sendEmail = this.$('.send-email');

            this.reviewRequestEditorView.publishDraft({
                trivial: $sendEmail.length === 1 && !$sendEmail.is(':checked')
            });

            return false;
        },


        /**
         * Handle a click on "Discard Draft".
         *
         * Discards the draft of the review request.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onDiscardDraftClicked: function _onDiscardDraftClicked() {
            this.reviewRequest.draft.destroy({
                error: function error(xhr) {
                    return alert(xhr.errorText);
                }
            });

            return false;
        },


        /**
         * Handle a click on "Discard Review Request".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onCloseDiscardedClicked: function _onCloseDiscardedClicked() {
            this.reviewRequest.close({
                type: RB.ReviewRequest.CLOSE_DISCARDED
            });

            return false;
        },


        /**
         * Render the banner.
         *
         * Returns:
         *     DraftBannerView:
         *     This object, for chaining.
         */
        render: function render() {
            var descriptionFieldClasses = [];

            if (this.reviewRequestEditor.get('mutableByUser')) {
                descriptionFieldClasses.push('editable');
            }

            var draft = this.reviewRequest.draft;

            if (draft.get('changeDescriptionRichText')) {
                descriptionFieldClasses.push('rich-text');
            }

            this.descriptionFieldClasses = descriptionFieldClasses.join(' ');
            this.descriptionFieldHTML = this.reviewRequestEditor.get('changeDescriptionRenderedText');

            BannerView.prototype.render.apply(this, arguments);

            var interdiffLink = draft.get('interdiffLink');

            if (interdiffLink) {
                this.$el.append(this._newDraftTemplate({
                    newDiffText: gettext('This draft adds a new diff.'),
                    showChangesText: gettext('Show changes'),
                    interdiffLink: interdiffLink
                }));
            }

            return this;
        }
    });

    /**
     * Manages the user-visible state of an editable review request.
     *
     * This owns the fields, thumbnails, banners, and general interaction
     * around editing a review request.
     */
    RB.ReviewRequestEditorView = Backbone.View.extend({
        defaultFields: [{
            fieldID: 'branch'
        }, {
            fieldID: 'bugs_closed',
            fieldName: 'bugsClosed',
            selector: '#field_bugs_closed',
            useEditIconOnly: true
        }, {
            fieldID: 'depends_on',
            fieldName: 'dependsOn',
            useEditIconOnly: true
        }, {
            fieldID: 'description',
            allowMarkdown: true
        }, {
            fieldID: 'summary'
        }, {
            fieldID: 'submitter',
            fieldName: 'submitter',
            useEditIconOnly: true
        }, {
            fieldID: 'target_groups',
            fieldName: 'targetGroups',
            useEditIconOnly: true
        }, {
            fieldID: 'target_people',
            fieldName: 'targetPeople',
            useEditIconOnly: true
        }, {
            fieldID: 'testing_done',
            fieldName: 'testingDone',
            allowMarkdown: true
        }],

        events: {
            'click #archive-review-request-link': '_onArchiveClicked',
            'click #unarchive-review-request-link': '_onUnarchiveClicked',
            'click #mute-review-request-link': '_onMuteClicked',
            'click #unmute-review-request-link': '_onUnmuteClicked'
        },

        _archiveActionsTemplate: _.template('<% if (visibility === RB.ReviewRequest.VISIBILITY_VISIBLE) { %>\n <li><a id="archive-review-request-link" href="#"><%- archiveText %></a></li>\n <li><a id="mute-review-request-link" href="#"><%- muteText %></a></li>\n<% } else if (visibility === RB.ReviewRequest.VISIBILITY_ARCHIVED) { %>\n <li><a id="unarchive-review-request-link" href="#"><%- unarchiveText %></a></li>\n<% } else if (visibility === RB.ReviewRequest.VISIBILITY_MUTED) { %>\n <li><a id="unmute-review-request-link" href="#"><%- unmuteText %></a></li>\n<% } %>'),

        /**
         * Initialize the view.
         */
        initialize: function initialize() {
            _.bindAll(this, '_checkResizeLayout', '_scheduleResizeLayout', '_onCloseDiscardedClicked', '_onCloseSubmittedClicked', '_onDeleteReviewRequestClicked', '_onUpdateDiffClicked', '_onArchiveClicked', '_onUnarchiveClicked', '_onMuteClicked', '_onUnmuteClicked', '_onUploadFileClicked');

            this._fieldViews = {};
            this.rendered = false;

            this.draft = this.model.get('reviewRequest').draft;
            this.banner = null;
            this._$main = null;
            this._$extra = null;
            this._blockResizeLayout = false;
        },


        /**
         * Add a view for a field in the review request.
         *
         * Args:
         *     view (RB.ReviewRequestFields.BaseFieldView):
         *         The view which handles editing for the field.
         */
        addFieldView: function addFieldView(view) {
            var _this2 = this;

            this._fieldViews[view.fieldID] = view;
            view.reviewRequestEditorView = this;

            this.listenTo(view, 'resize', this._scheduleResizeLayout);
            this.listenTo(view, 'fieldError', function (err) {
                _this2._$warning.delay(6000).fadeOut(400, function () {
                    return _this2._$warning.hide();
                }).html(err.errorText).show();
            });
            this.listenTo(view, 'fieldSaved', this.showBanner);

            if (this.rendered) {
                view.render();
            }
        },


        /**
         * Return the view for the field with the given ID.
         *
         * Args:
         *     fieldID (string):
         *        The ID of the field.
         *
         * Returns:
         *     RB.ReviewRequestFields.BaseFieldView:
         *     The view which handles editing for the field.
         */
        getFieldView: function getFieldView(fieldID) {
            return this._fieldViews[fieldID];
        },


        /**
         * Render the editor.
         *
         * This will import all pre-rendered file attachment and screenshot
         * thumbnails, turning them into FileAttachment and Screenshot objects.
         *
         * Returns:
         *     RB.ReviewRequestEditorView:
         *     This object, for chaining.
         */
        render: function render() {
            var _this3 = this;

            var reviewRequest = this.model.get('reviewRequest');
            var fileAttachments = this.model.get('fileAttachments');
            var draft = reviewRequest.draft;

            this._$box = this.$('.review-request');
            this._$warning = $('#review-request-warning');
            this._$screenshots = $('#screenshot-thumbnails');
            this._$attachments = $('#file-list');
            this._$attachmentsContainer = $(this._$attachments.parent()[0]);
            this._$bannersContainer = $('#review-request-banners');
            this._$main = $('#review-request-main');
            this._$extra = $('#review-request-extra');

            this.listenTo(reviewRequest, 'change:visibility', this._updateArchiveVisibility);
            this._updateArchiveVisibility();

            /*
             * We need to show any banners before we render the fields, since the
             * banners can add their own fields.
             */
            this.showBanner();

            if (this.model.get('editable')) {
                RB.DnDUploader.instance.registerDropTarget(this._$attachmentsContainer, gettext('Drop to add a file attachment'), this._uploadFile.bind(this));
            }

            $("#review-request-files-placeholder").remove();

            fileAttachments.each(function (fileAttachment) {
                return _this3.buildFileAttachmentThumbnail(fileAttachment, fileAttachments, { noAnimation: true });
            });

            this.listenTo(fileAttachments, 'add', this.buildFileAttachmentThumbnail);
            this.listenTo(fileAttachments, 'destroy', function () {
                if (fileAttachments.length === 0) {
                    _this3._$attachmentsContainer.hide();
                }
            });

            /*
             * Import all the screenshots and file attachments rendered onto
             * the page.
             */
            _.each(this._$screenshots.find('.screenshot-container'), this._importScreenshotThumbnail, this);
            _.each($('.binary'), this._importFileAttachmentThumbnail, this);

            // Render all the field views.
            for (var fieldID in this._fieldViews) {
                if (this._fieldViews.hasOwnProperty(fieldID)) {
                    this._fieldViews[fieldID].render();
                }
            }

            /*
             * Update the layout constraints any time these properties
             * change. Also, right away.
             */
            $(window).resize(this._scheduleResizeLayout);
            this.listenTo(this.model, 'change:editCount', this._checkResizeLayout);
            this._checkResizeLayout();

            this._setupActions();

            this.model.on('publishError', function (errorText) {
                alert(errorText);

                _this3.$('#btn-draft-publish').enable();
                _this3.$('#btn-draft-discard').enable();
            });

            this.model.on('closeError', function (errorText) {
                return alert(errorText);
            });
            this.model.on('saved', this.showBanner, this);
            this.model.on('published', this._refreshPage, this);
            reviewRequest.on('closed reopened', this._refreshPage, this);
            draft.on('destroyed', this._refreshPage, this);

            window.onbeforeunload = this._onBeforeUnload.bind(this);

            this.rendered = true;

            return this;
        },


        /**
         * Warn the user if they try to navigate away with unsaved comments.
         *
         * Args:
         *     evt (Event):
         *         The event that triggered the handler.
         *
         * Returns:
         *     string:
         *     The warning message.
         *
         */
        _onBeforeUnload: function _onBeforeUnload(evt) {
            if (this.model.get('editCount') > 0) {
                /*
                 * On IE, the text must be set in evt.returnValue.
                 *
                 * On Firefox, it must be returned as a string.
                 *
                 * On Chrome, it must be returned as a string, but you
                 * can't set it on evt.returnValue (it just ignores it).
                 */
                var msg = gettext('You have unsaved changes that will be lost if you navigate away from this page.');
                evt = evt || window.event;

                evt.returnValue = msg;
                return msg;
            }
        },


        /**
         * Show a banner for the given state of the review request.
         */
        showBanner: function showBanner() {
            if (this.banner) {
                return;
            }

            var reviewRequest = this.model.get('reviewRequest');
            var state = reviewRequest.get('state');
            var BannerClass = void 0;

            if (state === RB.ReviewRequest.CLOSE_SUBMITTED) {
                BannerClass = SubmittedBannerView;
            } else if (state === RB.ReviewRequest.CLOSE_DISCARDED) {
                BannerClass = DiscardedBannerView;
            } else if (state === RB.ReviewRequest.PENDING && this.model.get('hasDraft')) {
                BannerClass = DraftBannerView;
            } else {
                return;
            }

            var $existingBanner = this._$bannersContainer.children();

            console.assert(BannerClass);
            console.assert($existingBanner.length <= 1);

            if ($existingBanner.length === 0) {
                $existingBanner = undefined;
            }

            this.banner = new BannerClass({
                el: $existingBanner,
                reviewRequestEditorView: this
            });

            if ($existingBanner) {
                $existingBanner.show();
            } else {
                this.banner.$el.appendTo(this._$bannersContainer);
            }

            this.banner.render();
        },


        /**
         * Handle a click on the "Publish Draft" button.
         *
         * Begins publishing the review request. If there are any field editors
         * still open, they'll be saved first.
         *
         * Args:
         *     options (object):
         *         Options for the publish operation.
         */
        publishDraft: function publishDraft(options) {
            // Save all the fields if we need to.
            var fields = Object.values(this._fieldViews).filter(function (view) {
                return view.needsSave();
            });

            this.model.set({
                publishing: true,
                pendingSaveCount: fields.length
            });

            if (fields.length === 0) {
                this.model.publishDraft(options);
            } else {
                fields.forEach(function (field) {
                    return field.finishSave();
                });
            }
        },


        /**
         * Upload a dropped file as a file attachment.
         *
         * A temporary file attachment placeholder will appear while the
         * file attachment uploads. After the upload has finished, it will
         * be replaced with the thumbnail depicting the file attachment.
         *
         * Args:
         *     file (File):
         *         The file to upload.
         */
        _uploadFile: function _uploadFile(file) {
            // Create a temporary file listing.
            var fileAttachment = this.model.createFileAttachment();

            fileAttachment.set('file', file);
            fileAttachment.save();
        },


        /**
         * Set up all review request actions and listens for events.
         */
        _setupActions: function _setupActions() {
            var _this4 = this;

            var $closeDiscarded = this.$('#discard-review-request-action');
            var $closeSubmitted = this.$('#submit-review-request-action');
            var $deletePermanently = this.$('#delete-review-request-action');
            var $updateDiff = this.$('#upload-diff-action');
            var $uploadFile = this.$('#upload-file-action');

            /*
             * We don't want the click event filtering from these down to the
             * parent menu, so we can't use events above.
             */
            $closeDiscarded.click(this._onCloseDiscardedClicked);
            $closeSubmitted.click(this._onCloseSubmittedClicked);
            $deletePermanently.click(this._onDeleteReviewRequestClicked);
            $updateDiff.click(this._onUpdateDiffClicked);
            $uploadFile.click(this._onUploadFileClicked);

            RB.ReviewRequestActionHook.each(function (hook) {
                _.each(hook.get('callbacks'), function (selector, handler) {
                    return _this4.$(selector).click(handler);
                });
            });
        },


        /**
         * Build a thumbnail for a FileAttachment.
         *
         * The thumbnail will eb added to the page. The editor will listen
         * for events on the thumbnail to update the current edit state.
         *
         * This can be called either when dynamically adding a new file
         * attachment (through drag-and-drop or Add File), or after importing
         * from the rendered page.
         *
         * Args:
         *     fileAttachment (RB.FileAttachment):
         *         The file attachment.
         *
         *     collection (Backbone.Collection):
         *         The collection of all file attachments.
         *
         *     options (object):
         *         Options.
         *
         * Option Args:
         *     $el (jQuery):
         *         The thumbnail element, if it already exists in the DOM.
         */
        buildFileAttachmentThumbnail: function buildFileAttachmentThumbnail(fileAttachment, collection) {
            var _this5 = this;

            var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

            var fileAttachmentComments = this.model.get('fileAttachmentComments');
            var $thumbnail = options.$el;

            var view = new RB.FileAttachmentThumbnail({
                el: $thumbnail,
                model: fileAttachment,
                comments: fileAttachmentComments[fileAttachment.id],
                renderThumbnail: $thumbnail === undefined,
                reviewRequest: this.model.get('reviewRequest'),
                canEdit: this.model.get('editable') === true
            });

            view.render();

            if (!$thumbnail) {
                // This is a newly added file attachment.
                this._$attachmentsContainer.show();
                view.$el.insertBefore(this._$attachments.children('br'));

                if (!options.noAnimation) {
                    view.fadeIn();
                }
            }

            this.listenTo(view, 'hoverIn', function ($thumbnail) {
                _this5._$attachments.find('.file').not($thumbnail.find('.file')[0]).addClass('faded');
            });

            this.listenTo(view, 'hoverOut', function () {
                return _this5._$attachments.find('.file').removeClass('faded');
            });

            view.on('beginEdit', function () {
                return _this5.model.incr('editCount');
            });
            view.on('endEdit', function () {
                return _this5.model.decr('editCount');
            });
            view.on('commentSaved', function () {
                return RB.DraftReviewBannerView.instance.show();
            });
        },


        /**
         * Import file attachments from the rendered page.
         *
         * Each file attachment already rendered will be turned into a
         * FileAttachment, and a new thumbnail will be built for it.
         *
         * Args:
         *     thumbnailEl (Element):
         *         The existing DOM element to import.
         */
        _importFileAttachmentThumbnail: function _importFileAttachmentThumbnail(thumbnailEl) {
            var $thumbnail = $(thumbnailEl);
            var id = $thumbnail.data('file-id');
            var $caption = $thumbnail.find('.file-caption .edit');
            var reviewRequest = this.model.get('reviewRequest');
            var fileAttachment = reviewRequest.draft.createFileAttachment({
                id: id
            });

            if (!$caption.hasClass('empty-caption')) {
                fileAttachment.set('caption', $caption.text());
            }

            this.model.get('fileAttachments').add(fileAttachment, {
                $el: $thumbnail
            });
        },


        /**
         * Import screenshots from the rendered page.
         *
         * Each screenshot already rendered will be turned into a Screenshot.
         *
         * Args:
         *     thumbnailEl (Element):
         *         The existing DOM element to import.
         */
        _importScreenshotThumbnail: function _importScreenshotThumbnail(thumbnailEl) {
            var _this6 = this;

            var $thumbnail = $(thumbnailEl);
            var id = $thumbnail.data('screenshot-id');
            var reviewRequest = this.model.get('reviewRequest');
            var screenshot = reviewRequest.createScreenshot(id);
            var view = new RB.ScreenshotThumbnail({
                el: $thumbnail,
                model: screenshot
            });

            view.render();

            this.model.get('screenshots').add(screenshot);

            view.on('beginEdit', function () {
                return _this6.model.incr('editCount');
            });
            view.on('endEdit', function () {
                return _this6.model.decr('editCount');
            });
        },


        /**
         * Conditionally resize the layout.
         *
         * This is a wrapper for _resizeLayout that verifies that there's actually
         * a layout to resize.
         */
        _checkResizeLayout: function _checkResizeLayout() {
            /*
             * Not every page that uses this has a #review-request-main element
             * (for instance, review UIs want to have the draft banners but not
             * the review request box). In this case, just skip all of this.
             */
            if (this._$main.length !== 0 && !this._blockResizeLayout) {
                this._resizeLayout();
            }
        },


        /**
         * Resize the layout in response to size or position changes of fields.
         *
         * This will spread out the main text fields to cover the full height of
         * the review request box's main area. That helps keep a consistent look
         * and prevents a bunch of wasted-looking space.
         */
        _resizeLayout: function _resizeLayout() {
            var $lastContent = this._$main.children('.review-request-section:last-child');
            var $lastFieldContainer = $lastContent.children('.field-container');
            var $lastField = $lastFieldContainer.children('.editable');
            var lastFieldView = this._fieldViews[$lastField.data('field-id')];
            var lastContentTop = Math.ceil($lastContent.position().top);
            var editor = lastFieldView.inlineEditorView.textEditor;
            var detailsWidth = 300; // Defined as @details-width in reviews.less
            var detailsPadding = 10;
            var $detailsBody = $('#review-request-details tbody');
            var $detailsLabels = $detailsBody.find('th:first-child');
            var $detailsValues = $detailsBody.find('span');

            this._blockResizeLayout = true;

            /*
             * Make sure that the details fields wrap correctly, even if they don't
             * have wrappable characters (this combines with the white-space:
             * word-wrap: break-word style). This computation makes things handle
             * potentially unknown field labels correctly.
             */
            $detailsValues.css('max-width', detailsWidth - $detailsLabels.outerWidth() - detailsPadding * 3 + 'px');

            /*
             * Reset all the heights so we can do calculations based on their
             * native sizes.
             */
            this._$main.height('auto');
            $lastContent.height('auto');
            $lastField.height('auto');

            if (editor) {
                editor.setSize(null, 'auto');
            }

            /*
             * Set the review request box's main height to take up the full
             * amount of spaces between its top and the top of the "extra"
             * pane (where the issue summary table and stuff live).
             */
            this._$main.height(this._$extra.offset().top - this._$main.offset().top);
            var height = this._$main.height();

            if ($lastContent.outerHeight() + lastContentTop < height) {
                $lastContent.outerHeight(height - lastContentTop);

                /*
                 * Get the size of the content box, and factor in the padding at
                 * the bottom, to balance out position()'s calculation of the
                 * padding at the top. This ensures we get a height that matches
                 * the content area of the content box.
                 */
                var contentHeight = $lastContent.height() - Math.ceil($lastFieldContainer.position().top);

                /*
                 * Set the height of the editor or the editable field placeholder,
                 * depending on whether we're in edit mode. There's no need to do
                 * both, since this logic will be called again when the state
                 * changes.
                 */
                if (lastFieldView.inlineEditorView.editing() && editor) {
                    editor.setSize(null, contentHeight - lastFieldView.inlineEditorView.$buttons.height());
                } else {
                    /*
                     * It's possible to squish the editable element if we force
                     * a size, so make sure it's always at least the natural
                     * height.
                     */
                    var newEditableHeight = contentHeight + $lastField.getExtents('m', 'tb');

                    if (newEditableHeight > $lastField.outerHeight()) {
                        $lastField.outerHeight(newEditableHeight);
                    }
                }
            }

            this._blockResizeLayout = false;
        },


        /**
         * Schedule a layout resize after the stack unwinds.
         *
         * This will only trigger a layout resize after the stack has unwound,
         * and only once every 100 milliseconds at most.
         */
        _scheduleResizeLayout: _.throttleLayout(function () {
            this._checkResizeLayout();
        }, { defer: true }),

        /**
         * Handle a click on "Close -> Discarded".
         *
         * The user will be asked for confirmation before the review request is
         * discarded.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onCloseDiscardedClicked: function _onCloseDiscardedClicked() {
            var _this7 = this;

            var confirmText = gettext("Are you sure you want to discard this review request?");

            if (confirm(confirmText)) {
                this.model.get('reviewRequest').close({
                    type: RB.ReviewRequest.CLOSE_DISCARDED,
                    error: function error(model, xhr) {
                        return _this7.model.trigger('closeError', xhr.errorText);
                    }
                });
            }

            return false;
        },


        /**
         * Handle a click on "Close -> Submitted".
         *
         * If there's an unpublished draft, this will first confirm if the
         * user is sure.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onCloseSubmittedClicked: function _onCloseSubmittedClicked() {
            var _this8 = this;

            /*
             * This is a non-destructive event, so don't confirm unless there's
             * a draft.
             */
            var submit = true;

            if (this.banner) {
                submit = confirm(gettext("You have an unpublished draft. If you close this review request, the draft will be discarded. Are you sure you want to close the review request?"));
            }

            if (submit) {
                this.model.get('reviewRequest').close({
                    type: RB.ReviewRequest.CLOSE_SUBMITTED,
                    error: function error(model, xhr) {
                        return _this8.model.trigger('closeError', xhr.errorText);
                    }
                });
            }

            return false;
        },


        /**
         * Handle a click on "Close -> Delete Permanently".
         *
         * The user will be asked for confirmation before the review request is
         * deleted.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onDeleteReviewRequestClicked: function _onDeleteReviewRequestClicked() {
            var _this9 = this;

            var $dlg = $('<p>').text(gettext('This deletion cannot be undone. All diffs and reviews will be deleted as well.')).modalBox({
                title: gettext('Are you sure you want to delete this review request?'),
                buttons: [$('<input type="button" value="' + gettext('Cancel') + '"/>'), $('<input type="button" value="' + gettext('Delete') + '"/>').click(function () {
                    _this9.model.get('reviewRequest').destroy({
                        buttons: $('input', $dlg.modalBox('buttons')),
                        success: function success() {
                            window.location = SITE_ROOT;
                        }
                    });
                })]
            });

            return false;
        },


        /**
         * Handle a click on "Update -> Update Diff".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onUpdateDiffClicked: function _onUpdateDiffClicked() {
            var reviewRequest = this.model.get('reviewRequest');
            var updateDiffView = new RB.UpdateDiffView({
                model: new RB.UploadDiffModel({
                    changeNumber: reviewRequest.get('commitID'),
                    repository: reviewRequest.get('repository'),
                    reviewRequest: reviewRequest
                })
            });

            updateDiffView.render();

            return false;
        },


        /**
         * Handle a click on the "Add File" button.
         *
         * This method displays a popup for attachment upload.
         *
         * Args:
         *     e (Event):
         *         The event which triggered the action.
         */
        _onUploadFileClicked: function _onUploadFileClicked(e) {
            e.stopPropagation();
            e.preventDefault();

            var uploadDialog = new RB.UploadAttachmentView({
                reviewRequest: this.model.get('reviewRequest')
            });
            uploadDialog.render();
        },


        /**
         * Handle a click on "Archive -> Archive".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onArchiveClicked: function _onArchiveClicked() {
            return this._updateArchiveState(RB.UserSession.instance.archivedReviewRequests, true, RB.ReviewRequest.VISIBILITY_ARCHIVED);
        },


        /**
         * Handle a click on "Archive -> Unarchive".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onUnarchiveClicked: function _onUnarchiveClicked() {
            return this._updateArchiveState(RB.UserSession.instance.archivedReviewRequests, false, RB.ReviewRequest.VISIBILITY_VISIBLE);
        },


        /**
         * Handle a click on "Archive -> Mute".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onMuteClicked: function _onMuteClicked() {
            return this._updateArchiveState(RB.UserSession.instance.mutedReviewRequests, true, RB.ReviewRequest.VISIBILITY_MUTED);
        },


        /**
         * Handle a click on "Archive -> Unmute".
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _onUnmuteClicked: function _onUnmuteClicked() {
            return this._updateArchiveState(RB.UserSession.instance.mutedReviewRequests, false, RB.ReviewRequest.VISIBILITY_VISIBLE);
        },


        /**
         * Update archive/mute state.
         *
         * Args:
         *     collection (Backbone.Collection):
         *         The collection representing the user's archived or muted review
         *         requests.
         *
         *     add (boolean):
         *         True if the review request should be added to the collection
         *         (archived or muted), false if it shold be removed (unarchived or
         *         unmuted).
         *
         *     newState (number):
         *         The new state for the review request's ``visibility`` attribute.
         *
         * Returns:
         *     boolean:
         *     False, always.
         */
        _updateArchiveState: function _updateArchiveState(collection, add, newState) {
            var reviewRequest = this.model.get('reviewRequest');
            var options = {
                success: function success() {
                    return reviewRequest.set('visibility', newState);
                }
            };

            if (add) {
                collection.addImmediately(reviewRequest, options, this);
            } else {
                collection.removeImmediately(reviewRequest, options, this);
            }

            return false;
        },


        /**
         * Update the visibility of the archive/mute menu items.
         */
        _updateArchiveVisibility: function _updateArchiveVisibility() {
            var visibility = this.model.get('reviewRequest').get('visibility');

            this.$('#hide-review-request-menu').html(this._archiveActionsTemplate({
                visibility: visibility,
                archiveText: gettext('Archive'),
                muteText: gettext('Mute'),
                unarchiveText: gettext('Unarchive'),
                unmuteText: gettext('Unmute')
            }));

            var iconClass = visibility === RB.ReviewRequest.VISIBILITY_VISIBLE ? 'rb-icon-archive-off' : 'rb-icon-archive-on';

            this.$('#hide-review-request-link').html('<span class="rb-icon ' + iconClass + '"></span>');
        },


        /**
         * Refresh the page.
         */
        _refreshPage: function _refreshPage() {
            window.location = this.model.get('reviewRequest').get('reviewURL');
        }
    });
})();

//# sourceMappingURL=reviewRequestEditorView.js.map