'use strict';

(function () {

    var Fields = {};

    /**
     * Base class for all field views.
     */
    Fields.BaseFieldView = Backbone.View.extend({
        /**
         * The name of the property in the model for if this field is editable.
         */
        editableProp: 'editable',

        /** Whether the contents of the field should be stored in extraData. */
        useExtraData: true,

        /**
         * Initialize the view.
         *
         * Args:
         *     options (object):
         *         Options for the view.
         *
         * Option Args:
         *     fieldID (string):
         *         The ID of the field.
         */
        initialize: function initialize(options) {
            Backbone.View.prototype.initialize.call(this, options);
            this.options = options;
            this.fieldID = options.fieldID;
            this._fieldName = undefined;
            this.$el.data('field-id', this.fieldID);
        },


        /**
         * The name of the attribute within the model.
         *
         * Returns:
         *     string:
         *     The namee of the attribute that this field will reflect.
         */
        fieldName: function fieldName() {
            /*
             * This implementation will convert names with underscores to camel
             * case. This covers the typical naming between Python and JavaScript.
             * If subclasses need something different, they can override this with
             * either a new function or a regular attribute.
             */
            if (this._fieldName === undefined) {
                this._fieldName = this.fieldID.replace(/_(.)/g, function (m, c) {
                    return c.toUpperCase();
                });
            }

            return this._fieldName;
        },


        /**
         * Save a new value for the field.
         *
         * Args:
         *     value (*):
         *         The new value for the field.
         *
         *     options (object):
         *         Options for the save operation.
         */
        _saveValue: function _saveValue(value, options) {
            this.model.setDraftField(_.result(this, 'fieldName'), value, _.defaults(options, {
                jsonFieldName: this.jsonFieldName || this.fieldID,
                useExtraData: this.useExtraData
            }));
        },


        /**
         * Return whether the field has an unsaved editor open.
         *
         * This should be overridden by subclasses, if necessary.
         *
         * Returns:
         *     boolean:
         *     Whether the field is unsaved.
         */
        needsSave: function needsSave() {
            return false;
        },


        /**
         * Finish the field's save operation.
         *
         * This should be overridden by subclasses, if necessary.
         */
        finishSave: function finishSave() {}
    });

    /**
     * A field view for text-based fields.
     */
    Fields.TextFieldView = Fields.BaseFieldView.extend({
        /**
         * Autocomplete definitions.
         *
         * This should be overridden by subclasses.
         */
        autocomplete: null,

        /** Whether the view is multi-line or single line. */
        multiline: false,

        /** Whether the field allows Markdown-formatted text. */
        allowRichText: false,

        /**
         * Whether edits should be triggered only by clicking on the icon.
         *
         * If this is true, edits can only be triggered by clicking on the icon.
         * If this is false, clicks on the field itself will also trigger an edit.
         */
        useEditIconOnly: false,

        /**
         * The model attribute for if this field is rich text.
         *
         * This is the name of the attribute which indicates whether the field
         * contains Markdown-formatted text or plain text.
         *
         * Returns:
         *     string:
         *     The name of the model atribute indicating whether the field contains
         *     rich text.
         */
        richTextAttr: function richTextAttr() {
            return this.allowRichText ? _.result(this, 'fieldName') + 'RichText' : null;
        },


        /**
         * Return the type to use for the inline editor view.
         *
         * Returns:
         *     function:
         *     The constructor for the inline editor class to instantiate.
         */
        _getInlineEditorClass: function _getInlineEditorClass() {
            return this.allowRichText ? RB.RichTextInlineEditorView : RB.InlineEditorView;
        },


        /**
         * Render the view.
         *
         * Returns:
         *     RB.ReviewRequestFields.TextFieldView:
         *     This object, for chaining.
         */
        render: function render() {
            var _this = this;

            if (!this.$el.hasClass('editable')) {
                return this;
            }

            var fieldName = _.result(this, 'fieldName');
            var EditorClass = this._getInlineEditorClass();

            var inlineEditorOptions = {
                el: this.$el,
                formClass: this.$el.prop('id') + '-editor',
                editIconClass: 'rb-icon rb-icon-edit',
                enabled: this.model.get(this.editableProp),
                multiline: this.multiline,
                useEditIconOnly: this.useEditIconOnly,
                showRequiredFlag: this.$el.hasClass('required'),
                deferEventSetup: this.autocomplete !== null
            };

            if (this.allowRichText) {
                _.extend(inlineEditorOptions, {
                    textEditorOptions: {
                        minHeight: 0,
                        richText: this.model.getDraftField(_.result(this, 'richTextAttr'), { useExtraData: this.useExtraData })
                    },
                    matchHeight: false,
                    hasRawValue: true,
                    rawValue: this.model.getDraftField(fieldName, { useExtraData: this.useExtraData }) || ''
                });
            }

            this.inlineEditorView = new EditorClass(inlineEditorOptions);
            this.inlineEditorView.render();

            this.listenTo(this.inlineEditorView, 'beginEdit', function () {
                return _this.model.incr('editCount');
            });

            this.listenTo(this.inlineEditorView, 'cancel', function () {
                _this.trigger('resize');
                _this.model.decr('editCount');
            });

            this.listenTo(this.inlineEditorView, 'complete', function (value) {
                _this.trigger('resize');
                _this.model.decr('editCount');

                var jsonFieldName = _this.jsonFieldName || _this.fieldID;
                var saveOptions = {
                    allowMarkdown: _this.allowRichText,
                    error: function error(err) {
                        _this._formatField();
                        _this.trigger('fieldError', err);
                    },
                    success: function success() {
                        _this._formatField();
                        _this.trigger('fieldSaved');
                    }
                };

                if (_this.allowRichText) {
                    saveOptions.richText = _this.inlineEditorView.textEditor.richText;
                    saveOptions.jsonTextTypeFieldName = _this.fieldID === 'text' ? 'text_type' : jsonFieldName + '_text_type';
                }

                _this._saveValue(value, saveOptions);
            });

            if (this.autocomplete !== null) {
                this._buildAutoComplete();
                this.inlineEditorView.setupEvents();
            }

            this.listenTo(this.model, 'change:' + this.editableProp, function (model, editable) {
                if (editable) {
                    _this.inlineEditorView.enable();
                } else {
                    _this.inlineEditorView.disable();
                }
            });

            this.listenTo(this.model, 'fieldChanged:' + fieldName, this._formatField);

            return this;
        },


        /**
         * Convert an item to a hyperlink.
         *
         * Args:
         *     item (object):
         *         The item to link. The content is up to the caller.
         *
         *     options (object):
         *         Options to control the linking behavior.
         *
         * Option Args:
         *     cssClass (string, optional):
         *         The optional CSS class to add to the link.
         *
         *     makeItemText (function, optional):
         *         A function that takes the item and returns the text for the
         *         link. If not specified, the item itself will be used as the
         *         text.
         *
         *     makeItemURL (function, optional):
         *         A function that takes the item and returns the URL for the link.
         *         If not specified, the item itself will be used as the URL.
         *
         * Returns:
         *     jQuery:
         *     The resulting link element wrapped in jQuery.
         */
        _convertToLink: function _convertToLink(item) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            if (!item) {
                return $();
            }

            var $link = $('<a/>').attr('href', options.makeItemURL ? options.makeItemURL(item) : item).text(options.makeItemText ? options.makeItemText(item) : item);

            if (options.cssClass) {
                $link.addClass(options.cssClass);
            }

            return $link;
        },


        /**
         * Add auto-complete functionality to the field.
         */
        _buildAutoComplete: function _buildAutoComplete() {
            var ac = this.autocomplete;
            var reviewRequest = this.model.get('reviewRequest');

            this.inlineEditorView.$field.rbautocomplete({
                formatItem: function formatItem(data) {
                    var s = data[ac.nameKey];

                    if (ac.descKey && data[ac.descKey]) {
                        s += ' <span>(' + _.escape(data[ac.descKey]) + ')</span>';
                    }

                    return s;
                },
                matchCase: false,
                multiple: true,
                parse: function parse(data) {
                    var items = _.isFunction(ac.fieldName) ? ac.fieldName(data) : data[ac.fieldName];

                    return items.map(function (item) {
                        if (ac.parseItem) {
                            item = ac.parseItem(item);
                        }

                        return {
                            data: item,
                            value: item[ac.nameKey],
                            result: item[ac.nameKey]
                        };
                    });
                },
                url: SITE_ROOT + reviewRequest.get('localSitePrefix') + 'api/' + (ac.resourceName || ac.fieldName) + '/',
                extraParams: ac.extraParams,
                cmp: ac.cmp,
                width: 350,
                error: function error(xhr) {
                    var text = void 0;

                    try {
                        text = JSON.parse(xhr.responseText).err.msg;
                    } catch (e) {
                        text = 'HTTP ' + xhr.status + ' ' + xhr.statusText;
                    }

                    alert(text);
                }
            }).on('autocompleteshow', function () {
                /*
                 * Add the footer to the bottom of the results pane the
                 * first time it's created.
                 *
                 * Note that we may have multiple .ui-autocomplete-results
                 * elements, and we don't necessarily know which is tied to
                 * this. So, we'll look for all instances that don't contain
                 * a footer.
                 */
                var resultsPane = $('.ui-autocomplete-results:not(' + ':has(.ui-autocomplete-footer))');

                if (resultsPane.length > 0) {
                    $('<div/>').addClass('ui-autocomplete-footer').text(gettext('Press Tab to auto-complete.')).appendTo(resultsPane);
                }
            });
        },


        /**
         * Format the contents of the field.
         *
         * This will apply the contents of the model attribute to the field
         * element. If the field defines a ``formatValue`` method, this will use
         * that to do the formatting. Otherwise, the element will just be set to
         * contain the text of the value.
         */
        _formatField: function _formatField() {
            var value = this.model.getDraftField(_.result(this, 'fieldName'), { useExtraData: this.useExtraData });

            if (_.isFunction(this.formatValue)) {
                this.formatValue(value);
            } else {
                this.$el.text(value);
            }
        },


        /**
         * Return whether the field has an unsaved editor open.
         *
         * Returns:
         *     boolean:
         *     Whether the field is unsaved.
         */
        needsSave: function needsSave() {
            return this.inlineEditorView && this.inlineEditorView.isDirty();
        },


        /**
         * Finish the field's save operation.
         */
        finishSave: function finishSave() {
            this.inlineEditorView.submit();
        }
    });

    /**
     * A field view for multiline text-based fields.
     */
    Fields.MultilineTextFieldView = Fields.TextFieldView.extend({
        multiline: true,
        allowRichText: null,

        /**
         * Initialize the view.
         *
         * Args:
         *     options (object):
         *         Options for the view.
         */
        initialize: function initialize(options) {
            Fields.BaseFieldView.prototype.initialize.call(this, options);

            /*
             * If this field is coming from an extension which doesn't specify any
             * JS-side version, we need to pull some data out of the markup.
             */
            if (this.allowRichText === null) {
                this.allowRichText = this.$el.data('allow-markdown');

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

                var rawValue = this.$el.data('raw-value');
                extraData[this.fieldID] = rawValue !== undefined ? rawValue || '' : this.$el.text();
                this.$el.removeAttr('data-raw-value');

                if (this.allowRichText) {
                    extraData[_.result(this, 'richTextAttr')] = this.$el.hasClass('rich-text');
                }
            }
        },


        /**
         * Linkify a block of text.
         *
         * This turns URLs, /r/#/ paths, and bug numbers into clickable links. It's
         * a wrapper around RB.formatText that handles passing in the bug tracker.
         *
         * Args:
         *     options (object):
         *         Options for the text formatting.
         *
         * Option Args:
         *     newText (string, optional):
         *         The new text to format into the element. If not specified, the
         *         existing contents of the element are used.
         */
        formatText: function formatText(options) {
            var _this2 = this;

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

            options = _.defaults({
                bugTrackerURL: reviewRequest.get('bugTrackerURL'),
                isHTMLEncoded: true
            }, options);

            if (this.allowRichText) {
                var richTextAttr = _.result(this, 'richTextAttr');
                options.richText = this.model.getDraftField(richTextAttr, {
                    useExtraData: this.useExtraData
                });
            }

            RB.formatText(this.$el, options);

            this.$('img').load(function () {
                return _this2.trigger('resize');
            });
        },


        /**
         * Render the view.
         *
         * Returns:
         *     RB.ReviewRequestFields.MultilineTextFieldView:
         *     This object, for chaining.
         */
        render: function render() {
            Fields.TextFieldView.prototype.render.call(this);

            this.formatText();

            return this;
        },


        /**
         * Format the value into the field.
         *
         * Args:
         *     data (object):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            if (this.allowRichText) {
                this.formatText({ newText: data });
            }
        }
    });

    /**
     * A field view for fields that include multiple comma-separated values.
     */
    Fields.CommaSeparatedValuesTextFieldView = Fields.TextFieldView.extend({
        useEditIconOnly: true,

        /**
         * Convert an array of items to a list of hyperlinks.
         *
         * Args:
         *     list (Array);
         *         An array of items. The contents of the item is up to the caller.
         *
         *     options (object):
         *         Options to control the linking behavior.
         *
         * Option Args:
         *     cssClass (string, optional):
         *         The optional CSS class to add for each link.
         *
         *     makeItemText (function, optional):
         *         A function that takes an item and returns the text for the link.
         *         If not specified, the item itself will be used as the text.
         *
         *     makeItemURL (function, optional):
         *         A function that takes an item and returns the URL for the link.
         *         If not specified, the item itself will be used as the URL.
         *
         * Returns:
         *     jQuery:
         *     The resulting link elements in a jQuery list.
         */
        _urlizeList: function _urlizeList(list) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            var $links = $();

            if (list) {
                for (var i = 0; i < list.length; i++) {
                    $links = $links.add(this._convertToLink(list[i], options));

                    if (i < list.length - 1) {
                        $links = $links.add(document.createTextNode(', '));
                    }
                }
            }

            return $links;
        },


        /**
         * Format the value into the field.
         *
         * Args:
         *     data (Array):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            data = data || [];
            this.$el.html(data.join(', '));
        }
    });

    /**
     * A field view for checkbox fields.
     */
    Fields.CheckboxFieldView = Fields.BaseFieldView.extend({
        /**
         * Render the field.
         *
         * Returns:
         *     RB.ReviewRequestFields.CheckboxFieldView:
         *     This object, for chaining.
         */
        render: function render() {
            var _this3 = this;

            Fields.BaseFieldView.prototype.render.call(this);

            this.$el.change(function () {
                _this3._saveValue(_this3.$el.is(':checked'), {
                    error: function error(err) {
                        return _this3.trigger('fieldError', err);
                    },
                    success: function success() {
                        return _this3.trigger('fieldSaved');
                    }
                });
            });

            return this;
        }
    });

    /**
     * A field view for dropdown fields.
     */
    Fields.DropdownFieldView = Fields.BaseFieldView.extend({
        /**
         * Render the field.
         *
         * Returns:
         *     RB.ReviewRequestFields.DropdownFieldView:
         *     This object, for chaining.
         */
        render: function render() {
            var _this4 = this;

            Fields.BaseFieldView.prototype.render.call(this);

            this.$el.change(function () {
                _this4._saveValue(_this4.$el.val(), {
                    error: function error(err) {
                        return _this4.trigger('fieldError', err);
                    },
                    success: function success() {
                        return _this4.trigger('fieldSaved');
                    }
                });
            });

            return this;
        }
    });

    /**
     * A field view for date fields.
     */
    Fields.DateFieldView = Fields.TextFieldView.extend({
        /**
         * Render the field.
         *
         * Returns:
         *     RB.ReviewRequestFields.DateFieldView:
         *     This object, for chaining.
         */
        render: function render() {
            Fields.TextFieldView.prototype.render.call(this);

            this.inlineEditorView.$field.datepicker({
                changeMonth: true,
                changeYear: true,
                dateFormat: $.datepicker.ISO_8601,
                showButtonPanel: true
            });

            return this;
        },


        /**
         * Save a new value for the field.
         *
         * Args:
         *     value (*):
         *         The new value for the field.
         *
         *     options (object):
         *         Options for the save operation.
         */
        _saveValue: function _saveValue(value, options) {
            var m = moment(value, 'YYYY-MM-DD', true);

            if (!m.isValid()) {
                value = '';
                this.$el.text('');
            }

            Fields.TextFieldView.prototype._saveValue.call(this, value, options);
        }
    });

    /**
     * The "Branch" field.
     */
    Fields.BranchFieldView = Fields.TextFieldView.extend({
        useExtraData: false
    });

    /**
     * The "Bugs" field.
     */
    Fields.BugsFieldView = Fields.CommaSeparatedValuesTextFieldView.extend({
        useExtraData: false,

        /**
         * Format the value into the field.
         *
         * Args:
         *     data (Array):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            data = data || [];

            var reviewRequest = this.model.get('reviewRequest');
            var bugTrackerURL = reviewRequest.get('bugTrackerURL');

            if (bugTrackerURL) {
                this.$el.empty().append(this._urlizeList(data, {
                    makeItemURL: function makeItemURL(item) {
                        return bugTrackerURL.replace('--bug_id--', item);
                    },
                    cssClass: 'bug'
                })).find('.bug').bug_infobox();
            } else {
                this.$el.text(data.join(', '));
            }
        }
    });

    /**
     * The change description field.
     */
    Fields.ChangeDescriptionFieldView = Fields.MultilineTextFieldView.extend({
        allowRichText: true,
        jsonFieldName: 'changedescription',
        useExtraData: false
    });

    /**
     * The close description field.
     */
    Fields.CloseDescriptionFieldView = Fields.MultilineTextFieldView.extend({
        allowRichText: true,
        useExtraData: false,
        editableProp: 'statusEditable',

        /**
         * Save a new value for the field.
         *
         * Args:
         *     value (*):
         *         The new value for the field.
         *
         *     options (object):
         *         Options for the save operation.
         */
        _saveValue: function _saveValue(value, options) {
            this.model.get('reviewRequest').close(_.defaults({
                type: this.closeType,
                description: value,
                postData: {
                    force_text_type: 'html',
                    include_text_types: 'raw'
                }
            }, options));
        }
    });

    /**
     * The "Depends On" field.
     */
    Fields.DependsOnFieldView = Fields.CommaSeparatedValuesTextFieldView.extend({
        autocomplete: {
            fieldName: function fieldName(data) {
                return data.search.review_requests;
            },
            nameKey: 'id',
            descKey: 'id',
            display_name: 'summary',
            resourceName: 'search',
            parseItem: function parseItem(item) {
                item.id = item.id.toString();
                item.display_name = item.summary;

                return item;
            },
            extraParams: {
                summary: 1
            },
            cmp: function cmp(term, a, b) {
                return b.data.id - a.data.id;
            }
        },

        useEditIconOnly: true,
        useExtraData: false,

        /**
         * Format the value into the field.
         *
         * Args:
         *     data (Array):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            data = data || [];

            this.$el.empty().append(this._urlizeList(data, {
                makeItemURL: function makeItemURL(item) {
                    return item.url;
                },
                makeItemText: function makeItemText(item) {
                    return item.id;
                },
                cssClass: 'review-request-link'
            })).find('.review-request-link').review_request_infobox();
        }
    });

    /**
     * The "Description" field.
     */
    Fields.DescriptionFieldView = Fields.MultilineTextFieldView.extend({
        allowRichText: true,
        useExtraData: false
    });

    /**
     * The "Owner" field.
     */
    Fields.OwnerFieldView = Fields.TextFieldView.extend({
        autocomplete: {
            fieldName: 'users',
            nameKey: 'username',
            descKey: 'fullname',
            extraParams: {
                fullname: 1
            },
            cmp: function cmp(term, a, b) {
                /*
                 * Sort the results with username matches first (in alphabetical
                 * order), followed by real name matches (in alphabetical order).
                 */
                var aUsername = a.data.username;
                var bUsername = b.data.username;
                var aFullname = a.data.fullname;
                var bFullname = a.data.fullname;

                if (aUsername.indexOf(term) === 0) {
                    if (bUsername.indexOf(term) === 0) {
                        return aUsername.localeCompare(bUsername);
                    }

                    return -1;
                } else if (bUsername.indexOf(term) === 0) {
                    return 1;
                } else {
                    return aFullname.localeCompare(bFullname);
                }
            }
        },

        useEditIconOnly: true,
        useExtraData: false,

        /**
         * Format the value into the field.
         *
         * Args:
         *     data (string):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            var $link = this._convertToLink(data, {
                makeItemURL: function makeItemURL(item) {
                    var href = item.href;
                    return href.substr(href.indexOf('/users'));
                },
                makeItemText: function makeItemText(item) {
                    return item.title;
                },
                cssClass: 'user'
            });

            this.$el.empty().append($link.user_infobox());
        }
    });

    /**
     * The "Summary" field.
     */
    Fields.SummaryFieldView = Fields.TextFieldView.extend({
        useExtraData: false
    });

    /**
     * The "Groups" field.
     */
    Fields.TargetGroupsFieldView = Fields.CommaSeparatedValuesTextFieldView.extend({
        autocomplete: {
            fieldName: 'groups',
            nameKey: 'name',
            descKey: 'display_name',
            extraParams: {
                displayname: 1
            }
        },

        useEditIconOnly: true,
        useExtraData: false,

        /**
         * Format the value into the field.
         *
         * Args:
         *     data (Array):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            data = data || [];

            this.$el.empty().append(this._urlizeList(data, {
                makeItemURL: function makeItemURL(item) {
                    return item.url;
                },
                makeItemText: function makeItemText(item) {
                    return item.name;
                }
            }));
        }
    });

    /**
     * The "People" field.
     */
    Fields.TargetPeopleFieldView = Fields.CommaSeparatedValuesTextFieldView.extend({
        autocomplete: {
            fieldName: 'users',
            nameKey: 'username',
            descKey: 'fullname',
            extraParams: {
                fullname: 1
            },
            cmp: function cmp(term, a, b) {
                /*
                 * Sort the results with username matches first (in alphabetical
                 * order), followed by real name matches (in alphabetical order).
                 */
                var aUsername = a.data.username;
                var bUsername = b.data.username;
                var aFullname = a.data.fullname;
                var bFullname = a.data.fullname;

                if (aUsername.indexOf(term) === 0) {
                    if (bUsername.indexOf(term) === 0) {
                        return aUsername.localeCompare(bUsername);
                    }
                    return -1;
                } else if (bUsername.indexOf(term) === 0) {
                    return 1;
                } else {
                    return aFullname.localeCompare(bFullname);
                }
            }
        },

        useEditIconOnly: true,
        useExtraData: false,

        /**
         * Format the value into the field.
         *
         * Args:
         *     data (Array):
         *         The new value of the field.
         */
        formatValue: function formatValue(data) {
            data = data || [];
            this.$el.empty().append(this._urlizeList(data, {
                makeItemURL: function makeItemURL(item) {
                    return item.url;
                },
                makeItemText: function makeItemText(item) {
                    return item.username;
                },
                cssClass: 'user'
            })).find('.user').user_infobox();
        }
    });

    /**
     * The "Testing Done" field.
     */
    Fields.TestingDoneFieldView = Fields.MultilineTextFieldView.extend({
        allowRichText: true,
        useExtraData: false
    });

    RB.ReviewRequestFields = Fields;
})();

//# sourceMappingURL=reviewRequestFieldViews.js.map