/**
 * Site Header
 */
RB.HeaderView = Backbone.View.extend({
    SEARCH_SUMMARY_TRIM_LEN: 28,
    DESKTOP_SEARCH_RESULTS_WIDTH: 350,

    events: {
        'click #nav_toggle': '_handleNavClick',
        'touchstart #nav_toggle': '_handleNavClick',
        'focus #search_field': '_closeMobileMenu',
    },

    /**
     * Ensure that this HeaderView is the only one on the page.
     *
     * This exists in a separate function so that unit tests can disable this
     * check.
     */
    _ensureSingleton() {
        if (RB.HeaderView.instance !== null) {
            console.warn('There are two instances of RB.HeaderView on the ' +
                         'page. Make sure only one RB.PageView is ' +
                         'instantiated and registered through ' +
                         'RB.PageManager.setupPage().');
        } else {
            RB.HeaderView.instance = this;
        }
    },

    /**
     * Initialize the header.
     *
     * Args:
     *     options (object, optional):
     *         Options for the view.
     *
     * Option Args:
     *     $body (jQuery, optional):
     *         The body element. This is useful for unit tests.
     *
     *     $pageSidebar (jQuery, optional):
     *         The page sidebar element. This is useful for unit tests.
     */
    initialize(options={}) {
        this._ensureSingleton();

        this.options = options;

        /*
         * This is used by RB.PageManager to determine if a RB.PageView
         * subclass has correctly rendered a HeaderView, or if PageManager
         * needs to take care of it.
         *
         * This is deprecated and can be removed in Review Board 5.0.
         */
        this.isRendered = false;

        this._mobileMenuOpened = false;

        this._$window = null;
        this._$body = null;
        this._$navToggle = null;
        this._$mobileMenuMask = null;
    },

    /**
     * Remove the view from the DOM and disable event handling.
     *
     * This will also unset :js:attr:`RB.HeaderView.instance`, if currently
     * set to this instance.
     */
    remove() {
        if (RB.HeaderView.instance === this) {
            RB.HeaderView.instance = null;
        }

        if (this._$window) {
            this._$window.off('resize.rbHeaderView', this._recalcFunc);
        }

        Backbone.View.prototype.remove.call(this);
    },

    /**
     * Render the header.
     *
     * Returns:
     *     RB.HeaderView:
     *     This view, for chaining.
     */
    render() {
        const options = this.options;

        this._$window = $(window);
        this._$body = options.body || $(document.body);
        this._$navToggle = $('#nav_toggle');
        this._$mobileMenuMask = $('<div id="mobile_menu_mask">')
            .on('click touchstart', this._closeMobileMenu.bind(this))
            .insertAfter(options.$pageSidebar || $('#page-sidebar'));

        this._recalcFunc = _.throttle(this._recalcMobileMode.bind(this), 100);

        this._$window.on('resize.rbHeaderView', this._recalcFunc);
        this._recalcMobileMode();

        this._setupSearch();

        this.isRendered = true;

        return this;
    },

    /**
     * Handle a click on the mobile navigation menu.
     *
     * Args:
     *     e (Event):
     *         The click event.
     */
    _handleNavClick(e) {
        e.stopPropagation();
        e.preventDefault();

        this._setMobileMenuOpened(!this._mobileMenuOpened);
    },

    /**
     * Recalculate whether the header and nav menu is in mobile mode.
     */
    _recalcMobileMode() {
        const inMobileMode = this._$navToggle.is(':visible');

        if (inMobileMode === this.inMobileMode) {
            return;
        }

        if (!inMobileMode) {
            this._setMobileMenuOpened(false);
        }

        this.inMobileMode = inMobileMode;
        this.trigger('mobileModeChanged', inMobileMode);
    },

    /**
     * Close the mobile menu.
     *
     * This is used as an event handler, to make it easy to close the
     * mobile menu and prevent any bubbling or default actions from the
     * event.
     */
    _closeMobileMenu() {
        this._setMobileMenuOpened(false);

        return false;
    },

    /**
     * Set up the search auto-complete field.
     */
    _setupSearch() {
        this._$search = $('#search_field').rbautocomplete({
            formatItem: data => {
                let s;

                if (data.username) {
                    // For the format of users:
                    s = data.username;

                    if (data.fullname) {
                        s += ` <span>(${_.escape(data.fullname)})</span>`;
                    }

                } else if (data.name) {
                    // For the format of groups:
                    const displayName = _.escape(data.display_name);
                    s = `${data.name} <span>(${displayName})</span>`;
                } else if (data.summary) {
                    // For the format of review requests:
                    if (data.summary.length < this.SEARCH_SUMMARY_TRIM_LEN) {
                        s = data.summary;
                    } else {
                        s = data.summary.substring(
                            0, this.SEARCH_SUMMARY_TRIM_LEN);
                    }

                    s += ` <span>(${_.escape(data.id)})</span>`;
                }

                return s;
            },
            matchCase: false,
            multiple: false,
            clickToURL: true,
            selectFirst: false,
            width: this.DESKTOP_SEARCH_RESULTS_WIDTH,
            enterToURL: true,
            resultsParentEl: $('#page-container'),
            resultsClass: 'ui-autocomplete-results search-results',
            parse: data => {
                const objects = ['users', 'groups', 'review_requests'];
                const keys = ['username', 'name', 'summary'];

                const parsed = [];

                for (let j = 0; j < objects.length; j++) {
                    const object = objects[j];
                    const items = data.search[object];

                    for (let i = 0; i < items.length; i++) {
                        const value = items[i];
                        const key = keys[j];


                        if (j !== 2) {
                            // For users and groups, always show.
                            parsed.push({
                                data: value,
                                value: value[key],
                                result: value[key],
                            });
                        } else if (value.public) {
                            /*
                             * For review requests, only show ones that are
                             * public.
                             */
                            parsed.push({
                                data: value,
                                value: value[key],
                                result: value[key],
                            });
                        }
                    }
                }

                return parsed;
            },
            url: `${SITE_ROOT}api/search/`,
            extraParams: {
                'only-fields': 'display_name,fullname,id,name,public,summary,url,username',
                'only-links': '',
            },
        });
    },

    /**
     * Set whether the mobile menu is opened.
     *
     * Args:
     *     opened (boolean):
     *         Whether the menu is open.
     */
    _setMobileMenuOpened(opened) {
        if (opened === this._mobileMenuOpened) {
            return;
        }

        if (opened) {
            this._$mobileMenuMask.show();
            _.defer(() => this._$body.addClass('js-mobile-menu-open'));
        } else {
            this._$body.removeClass('js-mobile-menu-open');
            _.delay(() => this._$mobileMenuMask.hide(), 300);
        }

        this._mobileMenuOpened = opened;
    },
}, {
    /** The instance of the HeaderView. */
    instance: null,
});
