define(['jquery', 'ui/scroller', 'ui/keyNavigation/navigator', 'ui/keyNavigation/navigableDomElement', 'taoQtiTest/runner/plugins/content/accessibility/keyNavigation/helpers'], function ($, scrollHelper, keyNavigator, navigableDomElement, helpers) { 'use strict';

    $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
    scrollHelper = scrollHelper && Object.prototype.hasOwnProperty.call(scrollHelper, 'default') ? scrollHelper['default'] : scrollHelper;
    keyNavigator = keyNavigator && Object.prototype.hasOwnProperty.call(keyNavigator, 'default') ? keyNavigator['default'] : keyNavigator;
    navigableDomElement = navigableDomElement && Object.prototype.hasOwnProperty.call(navigableDomElement, 'default') ? navigableDomElement['default'] : navigableDomElement;

    /**
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; under version 2
     * of the License (non-upgradable).
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     *
     * Copyright (c) 2020 Open Assessment Technologies SA ;
     */

    /**
     * Add aria-labelledby attribute to choice interaction
     *
     * @param {Navigator} cursor
     */
    const addLabelledByAttribute = cursor => {
      const $element = cursor.navigable.getElement();
      const value = $element.attr('value');
      const name = $element.attr('name');
      if (name) {
        $element.attr('aria-labelledby', `${name.replace('response-', 'choice-')}-${value}`);
      }
    };

    /**
     * Add aria-labelledby attribute from choice interaction
     *
     * @param {Navigator} cursor
     */
    const removeLabelledByAttribute = cursor => {
      const $element = cursor.navigable.getElement();
      $element.removeAttr('aria-labelledby', '');
    };

    /**
     * Adds attributes on navigation focus and blur
     *
     * @param {Navigator} navigator
     */
    const manageLabelledByAttribute = navigator => {
      if (navigator) {
        navigator.on('focus', addLabelledByAttribute);
        navigator.on('blur', removeLabelledByAttribute); // applies WCAG behavior for the radio buttons
      }
    };

    /**
     * Key navigator strategy applying inside the item.
     * Navigable item content are interaction choices and body element with the special class "key-navigation-focusable".
     * @type {Object} keyNavigationStrategy
     */
    var itemNavigation = {
      name: 'item',
      /**
       * Builds the item navigation strategy.
       *
       * @returns {keyNavigationStrategy}
       */
      init() {
        var _this = this;
        this.keyNavigators = [];
        const config = this.getConfig();
        const $content = this.getTestRunner().getAreaBroker().getContentArea();

        /**
         * Gets the QTI choice element from the current position in the keyNavigation
         * @param {Object} cursor - The cursor definition supplied by the keyNavigator
         * @returns {jQuery} - The selected choice element
         */
        const getQtiChoice = function (cursor) {
          return cursor && cursor.navigable.getElement().closest('.qti-choice');
        };

        /**
         * Creates and registers a keyNavigator for the supplied list of elements
         * @param {jQuery} $elements - The list of navigable elements
         * @param {jQuery} group - The group container
         * @param {Boolean} [loop=false] - Allow cycling the list when a boundary is reached
         * @param {Number|Function} [defaultPosition=0] - The default position the group should set the focus on
         * @returns {keyNavigator} - the created navigator, if the list of element is not empty
         */
        const addNavigator = function ($elements, group) {
          let loop = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
          let defaultPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
          const elements = navigableDomElement.createFromDoms($elements);
          if (elements.length) {
            const navigator = keyNavigator({
              elements,
              group,
              loop,
              defaultPosition,
              propagateTab: false
            });
            _this.keyNavigators.push(navigator);
            return navigator;
          }
        };

        /**
         * Creates and setups a keyNavigator for the interaction inputs.
         * @param {jQuery} $elements - The list of navigable elements
         * @param {jQuery} group - The group container
         * @param {Boolean} [loop=false] - Allow cycling the list when a boundary is reached
         * @param {Number|Function} [defaultPosition=0] - The default position the group should set the focus on
         * @returns {keyNavigator} - The supplied keyNavigator
         */
        const addInputsNavigator = function ($elements, group, loop) {
          let defaultPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
          const navigator = addNavigator($elements, group, loop, defaultPosition);
          if (navigator) {
            helpers.setupItemsNavigator(navigator, config);
            helpers.setupClickableNavigator(navigator);

            // each choice is represented by more than the input, the style must be spread to the actual element
            navigator.on('focus', cursor => scrollHelper.scrollTo(getQtiChoice(cursor).addClass('key-navigation-highlight'), $content.closest('.content-wrapper'))).on('blur', cursor => getQtiChoice(cursor).removeClass('key-navigation-highlight'));
          }
          return navigator;
        };

        // list the navigable areas inside the item. This could be either the interactions choices or the prompts
        const $qtiInteractions = $content.find('.key-navigation-focusable,.qti-interaction')
        //filter out interaction as it will be managed separately
        .filter((i, node) => !$(node).parents('.qti-interaction').length);

        // the item focusable body elements are considered scrollable
        $content.find('.key-navigation-focusable').addClass('key-navigation-scrollable');

        // each navigable area will get its own keyNavigator
        $qtiInteractions.each((itemPos, itemElement) => {
          const $itemElement = $(itemElement);

          // detect the type of choices: checkbox or radio
          const $choiceInput = $itemElement.find('.qti-choice input');
          const choiceType = $choiceInput.attr('type');
          if ($itemElement.hasClass('qti-interaction')) {
            //interaction block may be scrollable (if writing-mode for interaction is different from writing-mode for item)
            if ($itemElement.hasClass('key-navigation-focusable')) {
              addNavigator($itemElement, $itemElement);
            }

            //add navigable elements from prompt
            $itemElement.find('.key-navigation-focusable').each((navPos, nav) => {
              const $nav = $(nav);
              if (!$nav.closest('.qti-choice').length) {
                addNavigator($nav, $nav);
              }
            });

            //reset interaction custom key navigation to override the behaviour with the new one
            $itemElement.off('.keyNavigation');

            //search for inputs that represent the interaction focusable choices
            const $inputs = $itemElement.is(':input') ? $itemElement : $itemElement.find(':input');
            if (config.flatNavigation && (config.flatRadioNavigation || choiceType !== 'radio')) {
              $inputs.each((i, input) => {
                const navigator = addInputsNavigator($(input), $itemElement);
                manageLabelledByAttribute(navigator);
              });
            } else {
              const navigator = addInputsNavigator($inputs, $itemElement, true, () => {
                // keep default positioning for now
                let position = -1;

                // autofocus the selected radio button if any
                $inputs.each((index, input) => {
                  if (input.checked) {
                    position = index;
                  }
                });
                return position;
              });
              manageLabelledByAttribute(navigator);

              // applies WCAG behavior for the radio buttons
              if (navigator && config.wcagBehavior) {
                navigator.on('focus', cursor => {
                  const $element = cursor.navigable.getElement();
                  if (!$element.is(':checked')) {
                    $element.click();
                  }
                });
              }
            }
          } else {
            addNavigator($itemElement, $itemElement);
          }
        });
        return this;
      },
      /**
       * Gets the list of applied navigators
       * @returns {keyNavigator[]}
       */
      getNavigators() {
        return this.keyNavigators;
      },
      /**
       * Tears down the keyNavigator strategy
       * @returns {keyNavigationStrategy}
       */
      destroy() {
        this.keyNavigators.forEach(navigator => navigator.destroy());
        this.keyNavigators = [];
        return this;
      }
    };

    return itemNavigation;

});
