/**
 * A view for managing state on a form.
 *
 * This provides some standard behavior for setting up form widgets and
 * handling collapsible fieldsets, along with managing subforms.
 */
RB.FormView = Backbone.View.extend({
    events: {
        'click .rb-c-form-fieldset__toggle': '_onToggleFieldSetClicked',
    },

    /**
     * Initialize the view.
     */
    initialize() {
        this._$subforms = null;
        this._subformsByGroup = {};
        this._formWidgetsInitialized = false;
    },

    /**
     * Render the view.
     *
     * This will set up any subforms that might be available within the form.
     *
     * Returns:
     *     RB.FormView:
     *     This object, for chaining.
     */
    render() {
        this._$subforms = this.$('.rb-c-form-fieldset.-is-subform');

        if (this._$subforms.length > 0) {
            this._setupSubforms();
        }

        this.setupFormWidgets();

        return this;
    },

    /**
     * Set up state for widgets on the form.
     *
     * This will ensure that widgets are set up correctly on the form, or on
     * a part of the form. This will take care to re-initialize widgets if
     * they've already been initialized before (useful when dynamically adding
     * new sections of a form).
     *
     * This supports only a few known types of widgets (Django date/time
     * widgets and related object selectors).
     *
     * Args:
     *     $el (jQuery, optional):
     *         A starting point for finding the widgets. If not provided, all
     *         widgets in the form will be set up.
     */
    setupFormWidgets($el) {
        if ($el === undefined) {
            $el = this.$el;
        }

        /*
         * Update some state for Django widgets. We've quite possibly made use
         * of widgets in the form that need to be initialized, and Django
         * doesn't have much fine-grained support for doing this, so we need
         * to take a heavy-handed approach.
         *
         * Django (up through 3.0 at least) performs similar logic.
         */
        if (window.DateTimeShortcuts &&
            $el.find('.datetimeshortcuts').length > 0) {
            if (this._formWidgetsInitialized) {
                /*
                 * Yep, we have to remove *all* of these... DateTimeShortcuts
                 * has no granular widget support.
                 */
                $('.datetimeshortcuts').remove();
            }

            DateTimeShortcuts.init();
        }

        if (window.SelectFilter) {
            $el.find('.selectfilter').each((i, el) => {
                const parts = el.name.split('-');
                SelectFilter.init(el.id, parts[parts.length - 1], false);
            });

            $el.find('.selectfilterstacked').each((i, el) => {
                const parts = el.name.split('-');
                SelectFilter.init(el.id, parts[parts.length - 1], true);
            });
        }

        this._formWidgetsInitialized = true;
    },

    /**
     * Set the visibility of one or more subforms.
     *
     * This will toggle visibility of a single subform, hide all subforms,
     * or hide all subforms except one.
     *
     * Args:
     *     options (object):
     *         Options to control visibility.
     *
     * Option Args:
     *     group (string):
     *         The registered group for the subforms.
     *
     *     hideOthers (boolean):
     *         Whether to hide any subforms other than the one specified by
     *         ``subformID``.
     *
     *     subformID (string):
     *         A single subform to set the visibility state for. If not
     *         provided, this will toggle visibility of all subforms in the
     *         group.
     *
     *     visible (boolean):
     *         Whether to make the selected subform visible. This is only used
     *         if ``hideOthers`` is not provided.
     */
    setSubformVisibility(options) {
        console.assert(_.isObject(options),
                       'An options object must be provided.');

        const group = options.group;
        const subformID = options.subformID;
        const visible = options.visible;

        console.assert(group, 'Missing option "group"');

        const subformIDs = this._subformsByGroup[group];
        console.assert(subformIDs, `Invalid subform group ${group}`);

        if (options.hideOthers || !subformID) {
            _.each(subformIDs, ($subform, id) => {
                const isHidden = (subformID === undefined
                                  ? !visible
                                  : (id !== subformID));

                $subform.prop({
                    disabled: isHidden,
                    hidden: isHidden,
                });
            });
        } else {
            console.assert(visible !== undefined, 'Missing option "visible"');

            const $subform = subformIDs[subformID];
            console.assert($subform, `Invalid subform ID ${subformID}`);

            $subform.prop({
                disabled: !visible,
                hidden: !visible,
            });
        }
    },

    /**
     * Set up state and event handlers for subforms.
     *
     * This will begin tracking all the subforms on the page, and connect
     * subform visibility to any associated controllers.
     */
    _setupSubforms() {
        const configuredControllers = {};

        this._$subforms.each((i, subformEl) => {
            const $subform = $(subformEl);
            const controllerID = $subform.data('subform-controller');
            const subformID = $subform.data('subform-id');
            const enablerID = $subform.data('subform-enabler');
            let group = $subform.data('subform-group');
            let $controller;
            let $enabler;

            if (!subformID) {
                console.error('Subform %o is missing data-subform-id=',
                              subformEl);
                return;
            }

            if (!group && !controllerID && !enablerID) {
                console.error(
                    'Subform %o is missing data-subform-group=, ' +
                    'data-subform-controller=, or data-subform-enable=',
                    subformEl);
                return;
            }

            /*
             * If we have a controller ID provided, look it up and ensure
             * we're using the right group.
             */
            if (controllerID) {
                $controller = this.$(`#${controllerID}`);

                console.assert($controller.length === 1,
                               `Missing controller #${controllerID}`);

                const controllerGroup =
                    $controller.data('subform-group');

                /*
                 * If the subform specifies an explicit group, and it
                 * specified a controller, make sure they match up. While
                 * we could work around an issue here, we'd rather make the
                 * developer fix their code.
                 */
                if (group === undefined) {
                    group = controllerGroup;
                } else if (controllerGroup !== group) {
                    console.error('Subform %o and controller %s have ' +
                                  'different values for data-subform-group',
                                  subformEl, controllerID);
                    return;
                }
            } else if (enablerID) {
                $enabler = this.$(`#${enablerID}`);
                window.$form = this.$el;

                console.assert($enabler.length === 1,
                               `Missing enabler #${enablerID}`);
            }

            /* Register the subforms so that they can be looked up later. */
            if (!this._subformsByGroup.hasOwnProperty(group)) {
                this._subformsByGroup[group] = {};
            }

            this._subformsByGroup[group][subformID] = $subform;

            /*
             * If we have a controller associated, set the current subform's
             * visibility based on that value, and listen for changes.
             */
            if ($controller) {
                this.setSubformVisibility({
                    group: group,
                    subformID: subformID,
                    visible: $controller.val() === subformID,
                });

                if (!configuredControllers[controllerID]) {
                    configuredControllers[controllerID] = true;

                    $controller.on('change', () => this.setSubformVisibility({
                        group: group,
                        subformID: $controller.val(),
                        visible: true,
                        hideOthers: true,
                    }));
                }
            }

            /*
             * If there's an enabler, set the current subform's visibility
             * based on the state of that element, and listen for changes.
             */
            if ($enabler) {
                const enabled = $enabler.is(':checked');

                $subform
                    .toggle(enabled)
                    .prop('disabled', !enabled);

                $enabler.on('change', () => {
                    const enabled = $enabler.is(':checked');

                    $subform
                        .toggle(enabled)
                        .prop('disabled', !enabled);
                });
            }
        });
    },

    /**
     * Handle the showing or collapsing of a fieldset.
     *
     * This will set the appropriate state on the fieldset to show or hide
     * the content.
     *
     * Args:
     *     e (jQuery.Event):
     *         The click event on the Show/Hide button.
     */
    _onToggleFieldSetClicked(e) {
        e.preventDefault();
        e.stopPropagation();

        const $toggle = $(e.target);
        const $fieldset = $toggle.closest('.rb-c-form-fieldset');

        if ($fieldset.hasClass('-is-collapsed')) {
            $fieldset.removeClass('-is-collapsed');
            $toggle.text(gettext('(Hide)'));
        } else {
            $fieldset.addClass('-is-collapsed');
            $toggle.text(gettext('(Show)'));
        }
    },
});
