/**
 * @license
 * Copyright (c) 2014, 2020, Oracle and/or its affiliates.
 * The Universal Permissive License (UPL), Version 1.0
 * @ignore
 */

define(['ojs/ojcore', 'jquery', 'ojs/ojcontext', 'ojs/ojthemeutils', 'ojs/ojtranslation', 'knockout', 'ojs/ojcomposite', 'ojs/ojcomponentcore', 'ojs/ojanimation', 'ojs/ojlogger', 'ojs/ojknockout', 
        'ojs/ojpopupcore', 'ojs/ojmessage', 'ojs/ojdataprovider'], 
function(oj, $, Context, ThemeUtils, Translations, ko, Composite, Components, AnimationUtils, Logger)
{
  "use strict";
var __oj_messages_metadata = 
{
  "properties": {
    "display": {
      "type": "string",
      "enumValues": [
        "general",
        "notification"
      ],
      "value": "general"
    },
    "displayOptions": {
      "type": "object",
      "properties": {
        "category": {
          "type": "string",
          "enumValues": [
            "auto",
            "header",
            "none"
          ],
          "value": "auto"
        }
      }
    },
    "messages": {
      "type": "Array<Object>|object"
    },
    "position": {
      "type": "object",
      "properties": {
        "at": {
          "type": "object",
          "properties": {
            "horizontal": {
              "type": "string",
              "enumValues": [
                "center",
                "end",
                "left",
                "right",
                "start"
              ]
            },
            "vertical": {
              "type": "string",
              "enumValues": [
                "bottom",
                "center",
                "top"
              ]
            }
          }
        },
        "collision": {
          "type": "string",
          "enumValues": [
            "fit",
            "flip",
            "flipfit",
            "none"
          ]
        },
        "my": {
          "type": "object",
          "properties": {
            "horizontal": {
              "type": "string",
              "enumValues": [
                "center",
                "end",
                "left",
                "right",
                "start"
              ]
            },
            "vertical": {
              "type": "string",
              "enumValues": [
                "bottom",
                "center",
                "top"
              ]
            }
          }
        },
        "of": {
          "type": "string"
        },
        "offset": {
          "type": "object",
          "properties": {
            "x": {
              "type": "number"
            },
            "y": {
              "type": "number"
            }
          }
        }
      }
    },
    "translations": {
      "type": "object",
      "value": {},
      "properties": {
        "ariaLiveRegion": {
          "type": "object",
          "properties": {
            "navigationFromKeyboard": {
              "type": "string"
            },
            "navigationToKeyboard": {
              "type": "string"
            },
            "navigationToTouch": {
              "type": "string"
            },
            "newMessage": {
              "type": "string"
            }
          }
        },
        "labelLandmark": {
          "type": "string"
        }
      }
    }
  },
  "methods": {
    "close": {},
    "closeAll": {},
    "setProperty": {},
    "getProperty": {},
    "setProperties": {},
    "getNodeBySubId": {},
    "getSubIdByNode": {}
  },
  "extension": {}
};
/* jslint browser: true*/


/* global ko:false, Components:false, Logger:false, Translations:false, ThemeUtils:false, Context:false */
/**
 * @ojcomponent oj.ojMessages
 * @since 5.0.0
 * @ojdisplayname Messages
 * @ojshortdesc Messages manages the layout and display of child messages.
 *
 * @ojsignature {target: "Type", value:"class ojMessages extends JetElement<ojMessagesSettableProperties>"}
 * @ojtsimport {module: "ojmessage", type: "AMD", imported: ["ojMessage"]}
 * @ojtsimport {module: "ojdataprovider", type: "AMD", imported: ["DataProvider"]}
 *
 * @ojpropertylayout {propertyGroup: "common", items: ["display", "position.at.horizontal", "position.at.vertical", "position.collision",
 *                                                     "position.my.horizontal", "position.my.vertical", "position.of",
 *                                                     "position.offset.x", "position.offset.y"]}
 * @ojpropertylayout {propertyGroup: "data", items: ["messages"]}
 * @ojvbdefaultcolumns 2
 * @ojvbmincolumns 1
 *
 * @classdesc
 * <h3 id="messageOverview-section">
 *   JET Messages
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#messageOverview-section"></a>
 * </h3>
 * <p>Description:
 * <p>Manages the layout and display of one or more messages.
 *
 * <h4 id="messages-syntax-section">Syntax
 * <a class="bookmarkable-link" title="Bookmarkable Link" href="#messages-syntax-section"></a></h4>
 * <p>Messages can be defined using a simple syntax to directly bind a DataProvider to the 'messages'
 *    attribute of oj-messages as shown below.
 * <p><b>Note that the use of inlined oj-message children inside of oj-messages is deprecated.</b>
 *    Applications should directly databind 'messages' attribute of oj-messages instead. Using
 *    oj-message inside of the 'messageTemplate' slot to define the template is still valid.
 *
 * <pre class="prettyprint">
 * <code>
 * &lt;oj-messages id="pageMessages" messages="[[messagesDataProvider]]">
 * &lt;/oj-messages>
 * </code>
 * </pre>
 *
 * <h4 id="messages-layout-section">Layouts
 * <a class="bookmarkable-link" title="Bookmarkable Link" href="#messages-layout-section"></a></h4>
 * <p>Messages can be shown in these layouts
 * <ul>
 * <li>Inlined to the page content</li>
 * <li>Overlayed on the page content, positioned to the top of the page by default</li>
 * <li>As notifications, positioned to the top-end corner of the page by default</li>
 * </ul>
 * Inlined messages will push the page contents when a new message is shown and could scroll out of
 * the view when user scrolls the page. Overlayed or notification style messages are usually
 * positioned relative to the window, will assume a fixed position and will remain
 * visible when the user scrolls the page. Overlayed or notification messages can be positioned
 * relative to another element in the page, in which case the messages follow that element upon
 * page scroll. Inline and overlay layouts are suitable for messages pertaining to the page or
 * region of the page, or relating to the task the user performed on the page. Notification layout is
 * suitable for messages that arrive asynchronously to the application or to communicate alerts to
 * users. Examples below shows the three layouts.
 *
 * <pre class="prettyprint">
 * <code>
 * &lt;!-- Inline: Do not specify the 'position', set 'display' to 'general' -->
 * &lt;oj-messages id="inlineMessages" messages="[[serviceRequestMessages]]" display="general">
 * &lt;/oj-messages>
 *
 * &lt;!-- Overlay: Set 'position' to an empty or a structured object, set 'display' to 'general' -->
 * &lt;oj-messages id="overlayMessages" messages="[[serviceRequestMessages]]" position="{}" display="general">
 * &lt;/oj-messages>
 *
 * &lt;!-- Notification: Set 'position' to an empty or a structured object, set 'display' to 'notification' -->
 * &lt;oj-messages id="notificationMessages" messages="[[emailMessages]]" position="{}" display="notification">
 * &lt;/oj-messages>
 * </code>
 * </pre>
 *
 * <h3 id="reparenting-section">
 *   Reparenting
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#reparenting-section"></a>
 * </h3>
 *
 *  <p id="reparenting-strategy">
 *     When the messages region is disclosed as a popup (position property defined), it will be
 *     reparented in the document and reparented back when all contained messages are closed.
 *     The location of the messages within the document will be in context of where it is defined.
 *     This is different than other types of popups where their reparenting location depends on
 *     the context they are used.  The stacking context of the messages region will be defined
 *     by the "oj-messages-layer" style class. The messages region that has active focus will be
 *     assigned a greater z-index value. This is applied to the messages's layer by way of
 *     the "oj-focus-within" pseudo selector applied with "oj-messages-layer" selector.
 *     The page author has control over z-index weights by way of the "oj-messages-layer" selector.
 *  </p>
 *
 * <h3 id="keyboard-section">
 *   Keyboard End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
 * </h3>
 *
 * {@ojinclude "name":"keyboardDoc"}
 *
 * <h3 id="styling-section">
 *   Styling
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#styling-section"></a>
 * </h3>
 * <p> The following CSS classes can be applied by the page author as needed.</p>
 *
 * {@ojinclude "name":"stylingDoc"}
 */

/**
 * <table class="keyboard-table">
 *   <thead>
 *     <tr>
 *       <th>Target</th>
 *       <th>Key</th>
 *       <th>Action</th>
 *     </tr>
 *   </thead>
 *   <tbody>
 *     <tr>
 *       <td rowspan = "3">Focus within Messages</td>
 *       <td><kbd>Tab</kbd> or <kbd>Shift + Tab</kbd></td>
 *       <td>Navigate the content of the messages region.</td>
 *     </tr>
 *     <tr>
 *       <td><kbd>F6</kbd></td>
 *       <td>Moves focus back to the last focused element outside the messages region.</td>
 *     </tr>
 *     <tr>
 *       <td><kbd>Esc</kbd></td>
 *       <td>Moves focus back to the last focused element outside the messages region.</td>
 *     </tr>
 *     <tr>
 *       <td rowspan = "1">Focus outside Messages</td>
 *       <td><kbd>F6</kbd></td>
 *       <td>Move focus to the first message within the more recently disclosed messages region.</td>
 *     </tr>
 *   </tbody>
 * </table>
 *
 * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
 * @memberof oj.ojMessages
 */

/**
 * <table class="generic-table styling-table">
 *   <thead>
 *     <tr>
 *       <th>Class</th>
 *       <th>Description</th>
 *     </tr>
 *   </thead>
 *   <tbody>
 *     <tr>
 *       <td>oj-messages-inline-remove-bottom-border</td>
 *       <td>Inline messages will include a bottom border so that the messages section is demarcated
 *            from the contents below it. If this border is not desirable for certain page layouts,
 *            it can be removed by setting this marker class on oj-messages.
  *       </td>
 *     </tr>
 *   </tbody>
 * </table>
 *
 * @ojfragment stylingDoc - Used in Styling section of classdesc, and standalone Styling doc
 * @memberof oj.ojMessages
 */

// Attributes
// ///////////

/**
 *<p> Specifies the collection of structured message data used to display the individual messages.
 * This property can be used to conveniently specify the required data as a single collection.
 * Individual message will be automatically created based on this data. See
 * {@link oj.ojMessage.Message} for message values.</p>
 * <p> More information about the structured 'Message' data can be found in documentation for
 * 'message' attribute of <code class="prettyprint">oj-message</code> element.</p>
 *
 * The collection can be two types:
 * <li>an array of oj.ojMessage.Message objects.</li>
 * <li>oj.ArrayDataProvider of oj.ojMessage.Message objects. Look at {@link oj.ArrayDataProvider} for more available options.</li>
 * @example <caption>Initialize component with <code class="prettyprint">messages</code> attribute:</caption>
 * //example with 'messages' attribute is  an array of oj.ojMessage.Message objects.
 * &lt;!-- emailNotifications is an array of messages, with each entry being of 'Message' type -->
 * &lt;oj-messages messages="[[emailNotifications]]">&lt;/oj-messages>
 * //example with 'messages' attribute of type dataprovider. See the documentation for {@link oj.ArrayDataProvider} for more details on the available options.
 * &lt;oj-messages messages="[[dataProvider]]">&lt;/oj-messages>
 * &lt;!-- dataProvider is an oj.ArrayDataProvider, with each entry being of 'Message' type -->
 * @example <caption>Get or set the <code class="prettyprint">messages</code> property after initialization:</caption>
 *
 * // getter
 * var messages = myMessages.messages;
 *
 * // setter example using a messages array
 * myMessages.messages = [{"severity": "error", "summary": "Some summary 1", "detail": "Some detail 1"},
 *                        {"severity": "warning", "summary": "Some summary 2", "detail": "Some detail 2"}];
 * // setter example using DataProvider
 * var messages = [{"severity": "error", "summary": "Some summary 1", "detail": "Some detail 1"},
 *                {"severity": "warning", "summary": "Some summary 2", "detail": "Some detail 2"}];
 * myMessages.messages = new oj.ArrayDataProvider(messages);
 * @expose
 * @type {null | Array.<Object> | Object}
 * @name messages
 * @ojshortdesc Specifies the collection of structured message data used to display the individual messages. See the Help documentation for more information.
 * @default null
 * @instance
 * @since 5.0.0
 * @access public
 * @memberof! oj.ojMessages
 * @ojsignature { target: "Type",
 *                value: "Array<oj.ojMessage.Message> | null | oj.DataProvider<any, oj.ojMessage.Message>",
 *                jsdocOverride: true}
**/

/**
 * <p>Specifies the display purpose of messages. The valid values for display are
 * <code class="prettyprint">general</code> and <code class="prettyprint">notification</code>.
 * General messages are commonly rendered at the page level or section level, relating to the task
 * the user performed. Notification messages are typically used for reporting asynchronous events,
 * or to communicate some background activity.</p>
 *
 * The presentation of the message is inline by default.  However, when a
 * {@link oj.ojMessages#position}property is provided, the presentation will be an overlay.
 * The alignment of the overlay will default based on the <code>display</code> property.  The
 * defaults are defined by the theme.
 *
 * @example <caption>Initialize component with <code class="prettyprint">display</code> attribute:</caption>
 * &lt;oj-messages display="notification" position="{}">&lt;/oj-messages>
 *
 * @example <caption>Get or set the <code class="prettyprint">display</code> property after initialization:</caption>
 * // getter
 * var display = myMessages.display;
 *
 * // setter
 * myMessages.display = "notification";
 *
 * @expose
 * @type {string}
 * @name display
 * @ojshortdesc Specifies the display purpose of the messages. See the Help documentation for more information.
 * @default "general"
 * @ojvalue {string} "general" messages pertaining to the page or region of the application
 * @ojvalue {string} "notification" often used for communicating alerts arriving asynchronously
 * @instance
 * @since 5.0.0
 * @access public
 * @memberof! oj.ojMessages
 **/

/**
 * <p>The position property defines the presentation style.  The default presentation is inline,
 * defined by a <code>null</code> position property value.  When a value is provide for the
 * property, the presentation style will be an overaly "popup".  The alignment of the overaly
 * is defined by the position sub-properties.</p>
 *
 * Default position sub-properites are extended by the provided value.  Defaults vary
 * based on the <code>display</code> property and provided by theme. The position
 * property is used to establish the location where the messages popup overlay will appear
 * relative to another element.</p>
 *
 * <p>The "my" and "at" properties defines alignment points relative to the popup and other
 * element.  The "my" property represents the popups alignment where the "at" property
 * represents the other element that can be identified by "of" or defauts to the launcher
 * when the popup opens.  The values of these properties describe horizontal and
 * vertical alignments.</p>
 *
 * @example <caption>Initialize the popup with <code class="prettyprint">position</code>
 *           attribute specified:</caption>
 * &lt;oj-messages position.my.horizontal="left"
 *              position.my.vertical="top"
 *              position.at.horizontal="right"
 *              position.at.vertical="top">
 * &lt;/oj-messages>;
 *
 * @example <caption>Get or set the <code class="prettyprint">position</code> property,
 *          after initialization:</caption>
 * // getter
 * var position = myMessages.position;
 *
 * // setter
 * myMessages.position =
 *    {"my": {"horizontal": "start", "vertical": "bottom"},
 *     "at": {"horizontal": "end", "vertical": "top" },
 *     "offset": {"x": 0, "y":5}};
 *
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @type {Object|null}
 * @name position
 * @ojshortdesc Specifies the position and presentation style of the messages. See the Help documentation for more information.
 * @ojsignature { target: "Type",
 *                value: "oj.ojMessages.Position|null",
 *                jsdocOverride: true}
 */

/**
 * Defines which edge on the messages overlay to align with the target ("of") element.
 *
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.my
 * @name position.my
 * @type {Object}
 */

/**
 * Defines the horizontal alignment of the messages overlay.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.my.horizontal
 * @name position.my.horizontal
 * @type {string}
 * @ojvalue {string} "start" evaluates to "left" in LTR mode and "right" in RTL mode.
 * @ojvalue {string} "end" evaluates to "right" in LTR mode and "left" in RTL mode.
 * @ojvalue {string} "left"
 * @ojvalue {string} "center"
 * @ojvalue {string} "right"
 */

/**
 * Defines the vertical alignment of the messages overlay.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.my.vertical
 * @name position.my.vertical
 * @type {string}
 * @ojvalue {string} "top"
 * @ojvalue {string} "center"
 * @ojvalue {string} "bottom"
 */

/**
 * Defines a point offset in pixels from the ("my") alignment.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.offset
 * @name position.offset
 * @type {Object}
 */

/**
 * Horizontal alignment offset.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.offset.x
 * @name position.offset.x
 * @type {number}
 */

/**
 * Vertical alignment offset.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.offset.y
 * @name position.offset.y
 * @type {number}
 */

/**
 * Defines which position on the target element ("of") to align the positioned element
 * against.
 *
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.at
 * @name position.at
 * @type {Object}
 */

/**
 * Defines the horizontal alignment of what the messages overlay is aligned to.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.at.horizontal
 * @name position.at.horizontal
 * @type {string}
 * @ojvalue {string} "start" evaluates to "left" in LTR mode and "right" in RTL mode.
 * @ojvalue {string} "end" evaluates to "right" in LTR mode and "left" in RTL mode.
 * @ojvalue {string} "left"
 * @ojvalue {string} "center"
 * @ojvalue {string} "right"
 */

/**
 * Defines the vertical alignment of what the messages overlay is aligned to.
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.at.vertical
 * @name position.at.vertical
 * @type {string}
 * @ojvalue {string} "top"
 * @ojvalue {string} "center"
 * @ojvalue {string} "bottom"
 */

/**
 * Which element to position the messages overlay against.
 *
 * If the value is a string, it should be a selector or the literal string value
 * of <code class="prettyprint">window</code>.  Otherwise, a point of x,y.  When a point
 * is used, the values are relative to the whole document.  Page horizontal and vertical
 * scroll offsets need to be factored into this point - see UIEvent
 * <a href="https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/pageX">pageX</a>,
 * <a href="https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/pageY">pageY</a>.
 *
 * @example <caption>Finding the point for an svg element:</caption>
 * var rect = svgDom.getBoundingClientRect();
 * var position = {of:{x:rect.left + window.pageXOffset, y:rect.top + window.pageYOffset}};
 *
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.of
 * @name position.of
 * @ojshortdesc Specifies which element to position the messages overlay against. See the Help documentation for more information.
 * @type {string}
 */

/**
 * Rule for alternate alignment.
 *
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @alias position.collision
 * @name position.collision
 * @type {string}
 * @ojvalue {string} "flip" the element to the opposite side of the target and the
 *  collision detection is run again to see if it will fit. Whichever side
 *  allows more of the element to be visible will be used.
 * @ojvalue {string} "fit" shift the element away from the edge of the window.
 * @ojvalue {string} "flipfit" first applies the flip logic, placing the element
 *  on whichever side allows more of the element to be visible. Then the fit logic
 *  is applied to ensure as much of the element is visible as possible.
 * @ojvalue {string} "none" no collision detection.
 */

/**
 * <p>Specifies the display options for contents of all the messages specified for the
 * {@link oj.ojMessages#messages} attribute.
 *
 * @expose
 * @member
 * @name displayOptions
 * @ojshortdesc Specifies the display options for contents of all the messages.
 * @memberof! oj.ojMessages
 * @instance
 * @since 6.1.0
 * @type {Object}
 * @ojsignature { target: "Type",
 *                value: "oj.ojMessage.DisplayOptions",
 *                jsdocOverride: true}
 *
 * @example <caption>Initialize the element with <code class="prettyprint">display-options</code>
 * attribute:</caption>
 * &lt;!-- Binding display-options attribute to a structured object -->
 * &lt;oj-messages display-options="[[myMessageDisplayOptions]]">&lt;/oj-message>
 *
 * &lt;!-- Setting display-options using JSON notation -->
 * &lt;oj-messages display-options='{"category": "none"}' message.summary="Some summary" message.detail="Some detail">&lt;/oj-messages>
 *
 * &lt;!-- Setting the display-options sub-attributes -->
 * &lt;oj-messages display-options.category="none" message.summary="Some summary" message.detail="Some detail">&lt;/oj-messages>
 *
 * @example <caption>Get or set the <code class="prettyprint">displayOptions</code> property after initialization:</caption>
 * // getter
 * var displayOptions = myMessages.displayOptions;
 *
 * // setter
 * myMessages.displayOptions = {
 *     category: "none"
 * };
 */

/**
 *<p>Specifies display option for {@link oj.ojMessage#message.category} text in all the messages
 * specified for the {@link oj.ojMessages#messages} attribute.
 *
 * @example <caption>Initialize the element with <code class="prettyprint">display-options.category</code> attribute:</caption>
 * &lt;oj-messages display-options.category="none" message.summary="Some summary" message.detail="Some detail">&lt;/oj-messages>
 *
 * @example <caption>Get or set the <code class="prettyprint">displayOptions.category</code> property after initialization:</caption>
 * // getter
 * var categoryOption = myMessages.getProperty("displayOptions.category");
 *
 * // setter
 * myMessages.setProperty("displayOptions.category", "none");
 *
 * @expose
 * @type {string}
 * @name displayOptions.category
 * @ojvalue {string} "header" if the {@link oj.ojMessage#message.category} property is specified,
 *  its value will be displayed in the header region of the message next to message icon. If
 *  {@link oj.ojMessage#message.category} property is not specified, a translated text corresponding
 *  to the value of the {@link oj.ojMessage#message.severity} property will be displayed.
 * @ojvalue {string} "auto" the component decides whether and where the
 *  {@link oj.ojMessage#message.category} text is displayed. The behavior is same as 'header'
 *  option, but may change in future releases.
 * @ojvalue {string} "none" the {@link oj.ojMessage#message.category} text will not be displayed
 * @default "auto"
 * @instance
 * @access public
 * @memberof! oj.ojMessages
 */

/**
 * <p>A collection of translated resources from the translation bundle, or <code class="prettyprint">null</code> if this
 * component has no resources.  Resources may be accessed and overridden individually or collectively, as seen in the examples.
 *
 * <p>If this component has translations, their documentation immediately follows this doc entry.
 *
 *
 * @example <caption>Initialize the component, overriding some translated resources and leaving the others intact:</caption>
 * &lt;!-- Using dot notation -->
 * &lt;oj-some-element translations.some-key='some value' translations.some-other-key='some other value'>&lt;/oj-some-element>
 *
 * &lt;!-- Using JSON notation -->
 * &lt;oj-some-element translations='{"someKey":"some value", "someOtherKey":"some other value"}'>&lt;/oj-some-element>
 *
 * @example <caption>Get or set the <code class="prettyprint">translations</code> property after initialization:</caption>
 * // Get one
 * var value = myComponent.translations.someKey;
 *
 * // Set one, leaving the others intact. Always use the setProperty API for
 * // subproperties rather than setting a subproperty directly.
 * myComponent.setProperty('translations.someKey', 'some value');
 *
 * // Get all
 * var values = myComponent.translations;
 *
 * // Set all.  Must list every resource key, as those not listed are lost.
 * myComponent.setProperty("translations.someKey" 'some value');
 *
 * @expose
 * @type {Object}
 * @name translations
 * @ojshortdesc A collection of translated resources from the translation bundle, or null if this component has no resources.
 * @memberof! oj.ojMessages
 * @ojtranslatable
 * @member
 * @instance
 * @since 5.0.0
 **/

// Slots
// //////

/**
 * <p>The <code class="prettyprint">&lt;oj-messages></code> element accepts only
 * <code class="prettyprint">&lt;oj-message></code> element as children for the default slot. The
 * default slot contents are rendered only if the 'messages' attribute on
 * <code class="prettyprint">&lt;oj-messages></code> is not set. If the 'messages' attribute is set
 * <code class="prettyprint">&lt;oj-message></code> children are automatically stamped for each
 * message data in the collection.
 *
 * <p><b>Note that the use of inlined oj-message children inside of oj-messages is deprecated.</b>
 *    Applications should directly databind 'messages' attribute of oj-messages instead. Using
 *    oj-message inside of the 'messageTemplate' slot to define the template is still valid.
 *
 * @ojchild Default
 * @ojshortdesc The oj-messages element accepts only oj-message elements as children for the default slot. See the Help documentation for more information.
 * @memberof oj.ojMessages
 * @since 5.0.0
 *
 * @example <caption>Initialize <code class="prettyprint">&lt;oj-messages></code> with explicitly
 * defined <code class="prettyprint">&lt;oj-message></code> children:</caption>
 * &lt;oj-messages id="inlineMessages">
 *   &lt;oj-message message='{"summary": "Some summary", "detail": "Some detail", "autoTimeout": 5000}'>&lt;/oj-message>
 *   &lt;oj-message message="[[surveyInstructions]]">&lt;/oj-message>
 *   &lt;oj-message message="[[surveySubmitConfirmation]]">&lt;/oj-message>
 * &lt;/oj-messages>
 */

/**
 * <p>The <code class="prettyprint">messageTemplate</code> slot is used to specify the template
 * for rendering each message in the <code class="prettyprint">oj-messages</code>. The slot
 * <b>must</b> be a &lt;template> element. The default template will display the
 * <code class="prettyprint">oj-message</code> children to best suit the display type of the
 * <code class="prettyprint">oj-messages</code>. This template slot will be applied only if the
 * 'messages' attribute on <code class="prettyprint">oj-messages</code>> is set.
 * The content of the template <b>must</b> be an <code class="prettyprint">oj-message</code>
 * element.</p>
 * <p>When the template is executed for each message, it will have access to the binding context
 * containing the following properties:</p>
 * <ul>
 *   <li>$current - an object that contains information for the current message. (See [oj.ojMessages.MessageTemplateContext]{@link oj.ojMessages.MessageTemplateContext} or the table
 *       below for a list of properties available on $current)</li>
 *  <li>alias - if 'data-oj-as' attribute was specified on the &lt;template> element, the value
 *      will be used to provide an application-named alias for $current.</li>
 * </ul>
 *
 *
 * @ojslot messageTemplate
 * @ojshortdesc The messageTemplate slot is used to specify the template for rendering each message. See the Help documentation for more information.
 * @ojmaxitems 1
 * @memberof oj.ojMessages
 * @since 6.2.0
 * @ojslotitemprops oj.ojMessages.MessageTemplateContext
 *
 * @example <caption>Initialize oj-messages with an inline message template specified:</caption>
 * &lt;oj-messages messages="[[dataProvider]]">
 *   &lt;template slot="messageTemplate">
 *     &lt;oj-message message="[[$current.data]]">&lt;/oj-message>
 *   &lt;template>
 * &lt;/oj-messages>
 */

// Methods
// ////////

/**
 * Closes the specified message regardless of the {@link oj.ojMessage#message.autoTimeout} or
 * {@link oj.ojMessage#message.closeAffordance} properties. The <code>message</code> argument
 * is a required argument, must be of type {@link oj.ojMessage#message}, and must be the same object
 * instance that was used to create and show the oj-message.
 *
 * <p>Closing a message changes the visibility to hidden. If the message is defined by an instance
 * in the {@link oj.ojMessages#messages} collection, the close operation will not remove the item
 * from the backing model. Application logic needs to listen for the
 * {@link oj.ojMessage#event:close} event bubbling up from the underlying oj-message child to remove
 * from the backing collection.</p>
 *
 * @expose
 * @function close
 * @ojshortdesc Unconditionally closes the specified message. See the Help documentation for more information.
 * @memberof! oj.ojMessages
 * @instance
 * @since 5.0.0
 * @param {Object} message the message to be closed
 * @return {void}
 * @ojsignature {target:"Type", value:"oj.ojMessage.Message", for:"message", jsdocOverride: true}
 *
 * @example <caption>Invoke the <code class="prettyprint">close</code> method:</caption>
 * myMessages.close(myMessage.message);
 */

/**
 * Closes child messages matching the <code>closeFilter</code> callback criteria.
 * The <code>closeFilter</code> callback is an optional argument. If not specified, all child
 * messages will be closed. If filter is specified, object of type {@link oj.ojMessage#message}
 * corresponding to each child will be passed to the <code>closeFilter</code> function. A return
 * value of <code>true</code> will result in calling {@link oj.ojMessage#close} for the message.
 * Otherwise, the message will remain in its current state.
 *
 * <p>Closing a message changes the visibility to hidden. If the message is defined by an instance
 * in the {@link oj.ojMessages#messages} collection, the close operation will not remove the item
 * from the backing model. Application logic needs to listen for the
 * {@link oj.ojMessage#event:close} event bubbling up from the underlying oj-message child to remove
 * from the backing collection.</p>
 *
 * @expose
 * @function closeAll
 * @ojshortdesc Closes all child messages matching an optional filter criteria. See the Help documentation for more information.
 * @memberof! oj.ojMessages
 * @instance
 * @since 5.0.0
 * @param {function} [closeFilter] an optional callback function that will be passed an instance
 *                   of type {@link oj.ojMessage#message} for each child
 *                   oj-message.  If <code>closeFilter</code> returns <code>true</code>, the
 *                   associated oj-message will be closed.  Returning <code>false</code> will
 *                   exclude the child message from closure.  If a <code>closeFilter</code> is not
 *                   passed, all child messages will be closed.
 * @return {void}
 * @ojsignature {target: "Type",
 *               value: "(message: oj.ojMessage.Message) => boolean",
 *               for: "closeFilter",
 *               jsdocOverride: true}
 *
 * @example <caption>Invoke the <code class="prettyprint">closeAll</code> method:</caption>
 * myMessages.closeAll(function (message)
 * {
 *   // close all messages of error severity
 *   return "error" === message.severity;
 * });
 */

/**
 * Sets a property or a single subproperty for complex properties and notifies the component
 * of the change, triggering a [property]Changed event.
 *
 * @function setProperty
 * @ojshortdesc Sets a property or a single subproperty for complex properties and notifies the component of the change, triggering a corresponding event.
 * @param {string} property - The property name to set. Supports dot notation for subproperty access.
 * @param {any} value - The new value to set the property to.
 * @return {void}
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @since 5.0.0
 *
 * @example <caption>Set a single subproperty of a complex property:</caption>
 * myComponent.setProperty('complexProperty.subProperty1.subProperty2', "someValue");
 */

/**
 * Retrieves a value for a property or a single subproperty for complex properties.
 * @function getProperty
 * @param {string} property - The property name to get. Supports dot notation for subproperty access.
 * @return {any}
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @since 5.0.0
 *
 * @example <caption>Get a single subproperty of a complex property:</caption>
 * var subpropValue = myComponent.getProperty('complexProperty.subProperty1.subProperty2');
 */

/**
 * Performs a batch set of properties.
 * @function setProperties
 * @param {Object} properties - An object containing the property and value pairs to set.
 * @return {void}
 * @expose
 * @memberof! oj.ojMessages
 * @instance
 * @since 5.0.0
 *
 * @example <caption>Set a batch of properties:</caption>
 * myComponent.setProperties({"prop1": "value1", "prop2.subprop": "value2", "prop3": "value3"});
 **/


// Type Defs
// //////////

/**
 * @typedef {Object} oj.ojMessages.PositionAlign
 * @property {"top"|"bottom"|"center"} [vertical] Vertical alignment.
 * @property {"start"|"end"|"left"|"center"|"bottom"} [horizontal] Horizontal alignment. <p>
 * <ul>
 *  <li><b>"start"</b> evaluates to "left" in LTR mode and "right" in RTL mode.</li>
 *  <li><b>"end"</b> evaluates to "right" in LTR mode and "left" in RTL mode.</li>
 * </ul>
 *
 */

/**
 * @typedef {Object} oj.ojMessages.PositionPoint
 * @property {number} [x] Horizontal alignment offset.
 * @property {number} [y] Vertical alignment offset.
 */
/**
 * @typedef {Object} oj.ojMessages.MessageTemplateContext
 * @property {Element} componentElement The &lt;oj-messages> custom element.
 * @property {Object} data The data for the current message being rendered.
 * @ojsignature {target:"Type", value:"oj.ojMessage.Message", for:"data", jsdocOverride: true}
 */

/**
 * @typedef {Object} oj.ojMessages.Position
 * @property {Object} [my] Defines which edge on the popup to align with the target ("of") element.
 * @property {Object} [at] Defines which position on the target element ("of") to align the positioned element
 *                                  against.
 * @property {Object} [offset] Defines a point offset in pixels from the ("my") alignment.
 * @property {string|Object} [of] Which element to position the popup against.
 *
 * If the value is a string, it should be a selector or the literal string value
 * of <code class="prettyprint">window</code>.  Otherwise, a point of x,y.  When a point
 * is used, the values are relative to the whole document.  Page horizontal and vertical
 * scroll offsets need to be factored into this point - see UIEvent
 * <a href="https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/pageX">pageX</a>,
 * <a href="https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/pageY">pageY</a>.
 *
 * @property {"flip"|"fit"|"flipfit"|"none"} [collision] Rule for alternate alignment. <p>
 * <ul>
 *  <li><b>"flip"</b> the element to the opposite side of the target and the
 *             collision detection is run again to see if it will fit. Whichever side
 *             allows more of the element to be visible will be used. </li>
 * <li><b>"fit"</b> shift the element away from the edge of the window. </li>
 * <li><b>"flipfit"</b> first applies the flip logic, placing the element
 *  on whichever side allows more of the element to be visible. Then the fit logic
 *  is applied to ensure as much of the element is visible as possible.</li>
 * <li><b>"none"</b> no collision detection.</li>
 * </ul>
 * @ojsignature [{target:"Type", value:"oj.ojMessages.PositionAlign", for:"my", jsdocOverride:true},
 *               {target:"Type", value:"oj.ojMessages.PositionAlign", for:"at", jsdocOverride:true},
 *               {target:"Type", value:"oj.ojMessages.PositionPoint", for:"offset", jsdocOverride:true},
 *               {target:"Type", value:"string|oj.ojMessages.PositionPoint", for:"of", jsdocOverride:true}]
 */

var _MESSAGES_VIEW =
  '<div role="presentation" :id="[[containerId]]" :class="[[containerSelectors]]" ' +
  '     on-oj-open="[[handleOpen]]" on-oj-close="[[handleClose]]" ' +
  '     on-oj-animate-start="[[handleAnimateStart]]">' +
  '  <oj-bind-if test="[[!$properties.messages]]">' +
  '    <oj-bind-slot>' +
  '    </oj-bind-slot>' +
  '  </oj-bind-if>' +
  '  <oj-bind-if test="[[$properties.messages]]">' +
  '    <oj-bind-for-each data="[[$properties.messages]]" >' +
  '      <template>' +
  '        <oj-bind-template-slot name="messageTemplate" ' +
  '          data="[[{data:$current.data, componentElement:_composite}]]">' +
  '          <template>' +
  '            <oj-message message="[[$current.data]]" display-options="[[$properties.displayOptions]]">' +
  '            </oj-message>' +
  '          </template>' +
  '        </oj-bind-template-slot>' +
  '      </template>' +
  '    </oj-bind-for-each>' +
  '  </oj-bind-if>' +
  '</div>';

function MessagesViewModel(context) {
  this._composite = context.element;

  // Anything used by the view bindings or component methods can't be obfuscated
  // and is why it's defined in quotes.
  this.containerId = [context.unique, 'mc'].join('_');
  this._messagesContainerId = this.containerId;
  this.handleOpen = this._handleOpen.bind(this);
  this.handleClose = this._handleClose.bind(this);
  this.handleAnimateStart = this._handleAnimateStart.bind(this);
  this.bindingsApplied = this._bindingsApplied.bind(this);
  this.disconnected = this._disconnected.bind(this);
  this.connected = this._connected.bind(this);
  this.close = this._close.bind(this);
  this.closeAll = this._closeAll.bind(this);
  this.propertyChanged = this._propertyChanged.bind(this);

  this._properties = context.properties;
  this._createObservables();
  this._updateLandmark();

  //  When slideIn/out animation is used, like the defaults in notification messages, the
  //  the transformation starts from off viewport. For applications that use oj-messages inside of
  //  iFrame with 100% width/height *(eg. our cookbook demos), this works find in all cases except
  //  WebKit. WebKit has a bug that 100% is not respected, and iFrame is resized to accomodate its
  //  contents. In this case the transformation starting from outside the iFrame bounds triggers a
  //  resize, the resize event then results in popupservice calling in the refresh hook (the
  //  _refresh() method here), which will again position to top-end for notification message, which
  //  will be further off view-port further trigger resize and goes into blind look.
  // Workaround is to set overflow hidden on the containing oj-messages so that transformation due
  //  to animation is confined to its boundary, this will workaround iFrame resize bug in Webkit.
  if (oj.AgentUtils.getAgentInfo().os === oj.AgentUtils.OS.IOS) {
    this._composite.style.overflow = 'hidden';
  }
}

MessagesViewModel.prototype._bindingsApplied = function () {
  // detects F6 navigation to the body content
  var messagesContainerDiv = document.getElementById(this._messagesContainerId);
  messagesContainerDiv.addEventListener('ojFocus', this._navigationEventListener.bind(this), false);

  // If 'messages' property is specified, we will not have inlined oj-message children.
  if (this._properties.messages) {
    // Defer showing messages until the first stamped oj-message child is created and ready to show.
    //  This is dealt with in beforeOpen handler. Animation override is dealt with in the
    //  animateStart listener.
    messagesContainerDiv.addEventListener('ojBeforeOpen', this._handleBeforeOpen.bind(this), false);
    return;
  }

  // If we had inlined oj-message children in the default slot, they are all created before the
  //  parent oj-messages is created, so oj-messages does not receive events from the inlined
  //  children. Deal with animate overriding open action for those messages here.
  var inlinedMessageChildren = this._getDefaultSlotMessageElements();
  if (inlinedMessageChildren.length !== 0) {
    // Add a busy state before we start animating open action for all inlined oj-message children
    var busyContext = Context.getContext(this._composite).getBusyContext();
    this._inlinedMessagesOpenBusyStateResolve =
      busyContext.addBusyState({ description: 'oj-messages is busy opening inlined messages' });

    this._showMessagesContainer();
    for (var i = 0; i < inlinedMessageChildren.length; i++) {
      this._animateMessageAction(
        inlinedMessageChildren[i].firstChild,
        'open',
        this._resolveInlinedMessagesOpenBusyState(
          inlinedMessageChildren[i].getProperty('message'),
          inlinedMessageChildren.length));
    }
  }
};

MessagesViewModel.prototype._resolveInlinedMessagesOpenBusyState = function (
  message,
  numInlinedChildren) {
  this._updateLiveRegionAndContainer(message);

  this._numInlinedChildrenAnimated =
    this._numInlinedChildrenAnimated ? this._numInlinedChildrenAnimated + 1 : 1;

  if (this._numInlinedChildrenAnimated === numInlinedChildren) {
    this._numInlinedChildrenAnimated = 0;
    // Now that we are done open animating all inlined children, resolve the busy context
    this._inlinedMessagesOpenBusyStateResolve();
  }
};

MessagesViewModel.prototype._disconnected = function () {
  MessagesViewModel.NAVIGATION_TRACKER.remove(this._messagesContainerId);

  // detaching an open message overlay results in implicit dismissal
  if (oj.ZOrderUtils.getStatus(this._composite) === oj.ZOrderUtils.STATUS.OPEN) {
    this._closeOverlay();
  }
};

MessagesViewModel.prototype._connected = function () {
  MessagesViewModel.NAVIGATION_TRACKER.add(this._messagesContainerId);
};

MessagesViewModel.prototype._closeAll = function (closeFilter) {
  if (this._isMessagesShown()) {
    var messageChildren = this._getDefaultSlotMessageElements();
    for (var i = 0; i < messageChildren.length; i++) {
      var message = messageChildren[i].message;
      var closeable = true;
      if (closeFilter) {
        closeable = closeFilter(message);
      }
      if (closeable) {
        messageChildren[i].close();
      }
    }
  }
};

MessagesViewModel.prototype._propertyChanged = function (detail) {
  if (detail.updatedFrom === 'external' && detail.property === 'position') {
    if (detail.previousValue && detail.value) {
      // re-evaluate overlay position
      this._refresh();
    } else if (!detail.previousValue && detail.value) {
      // inline to overlay
      if (this._getDefaultSlotMessageElements().length > 0) {
        if (this._isMessagesShown()) {
          this._hideMessages();
        }

        this._openOverlay();
      }
    } else if (detail.previousValue && !detail.value) {
      // overlay to inline
      if (this._getDefaultSlotMessageElements().length > 0) {
        if (this._isOverlayOpen()) {
          this._closeOverlay();
        }

        this._showMessages();
      }
    }
  } else if (detail.updatedFrom === 'external' && detail.property === 'display') {
    if (this._getDefaultSlotMessageElements().length > 0) {
      if (this._isOverlayOpen()) {
        this._closeOverlay();
        this._openOverlay();
        this._refresh();
      } else if (this._isMessagesShown()) {
        this._hideMessages();
        this._showMessages();
      }
    }
  } else if (detail.updatedFrom === 'external' && detail.property === 'translations') {
    this._updateLandmark();
  }
};

MessagesViewModel.prototype._close = function (message) {
  if (message && this._isMessagesShown()) {
    var messageChildren = this._getDefaultSlotMessageElements();
    for (var i = 0; i < messageChildren.length; i++) {
      /** @type {?} */
      var childMessage = $(messageChildren[i]).prop('message');
      if (childMessage === message) {
        messageChildren[i].close();
      }
    }
  }
};

MessagesViewModel.prototype._isEventPertaining = function (event) {
  // verify the event is from a child message
  var target = event.target;
  var messagesContainerDiv = document.getElementById(this._messagesContainerId);
  if (target.nodeName !== 'OJ-MESSAGE' || !oj.DomUtils.isAncestor(messagesContainerDiv, target)) {
    return false;
  }
  return true;
};

MessagesViewModel.prototype._handleBeforeOpen = function (event) {
  if (!event.defaultPrevented && this._isEventPertaining(event) && !this._isMessagesShown()) {
    this._showMessagesContainer();
  }
};

MessagesViewModel.prototype._showMessagesContainer = function () {
  if (!this._isPresentationInline()) {
    this._openOverlay();
  } else {
    this._showMessages();
  }
};

MessagesViewModel.prototype._handleOpen = function (event) {
  // verify the event is from a child message
  if (event.defaultPrevented || !this._isEventPertaining(event)) {
    return;
  }

  this._updateLiveRegionAndContainer(event.detail.message);
};

MessagesViewModel.prototype._updateLiveRegionAndContainer = function (message) {
  var translations = Translations.getComponentTranslations('oj-ojMessage').categories;

  // oj.Message has 'fatal' severity which is no different from 'error', oj-message does not support
  //  'fatal' for this reason. Map 'fatal' to 'error' just to be compatible with cases where the
  //  message stream could come from existing oj.Message sources.
  var severity = message.severity === 'fatal' ? 'error' : message.severity;
  var category = !message.category ? translations[severity] : message.category;
  var options = { category: category, summary: message.summary };

  var liveRegion = this._getLiveRegion();
  var text = this._getTranslationsDefault('ariaLiveRegion.newMessage', options);
  liveRegion.announce(text);

  this._refresh();  // re-evaluate the position as the overlay size can change.
};

MessagesViewModel.prototype._getTranslationsDefault = function (key, options) {
  var val = this._properties.translations;
  var keySegments = key.split('.');

  // key is a dot separated qualifier, break it so we can use in [] notation to access sub-props
  for (var i = 0; (i < keySegments.length) && val; i++) {
    val = val[keySegments[i]];
  }

  if (oj.StringUtils.isEmptyOrUndefined(val)) {
    val = Translations.getTranslatedString(['oj-ojMessages', key].join('.'), options);
  } else if (options) {
    // if app dev specified and we have params, insert those for tokens possible in the val
    val = Translations.applyParameters(val, options);
  }

  return val;
};

MessagesViewModel.prototype._handleClose = function (event) {
  // verify the event is from a child message
  if (event.defaultPrevented || !this._isEventPertaining(event)) {
    return;
  }

  // look for next focus
  var closeMessageElement = event.target;
  var nextFocusElement;

  // If the event was triggered from the close icon, the originalEvent will be populated
  // Next focus is only needed when closure was from the close icon.  autoTimeout closure
  // shouldn't insist on focus handling.
  if (event._originalEvent) {
    nextFocusElement = this._getNextFocus(closeMessageElement);
  }

  if (nextFocusElement) {
    nextFocusElement.focus();
  }

  // remove the oj-message from DOM. Note that we are not calling 'oj.Components.subtreeHidden'
  //  because it is not required if we use jquery remove() as per the doc there
  $(closeMessageElement).remove();

  // if we do not have any "default" slot children, then the message being closed is the last one,
  //  close/hide the containers hence
  if (this._getDefaultSlotMessageElements().length === 0) {
    MessagesViewModel.NAVIGATION_TRACKER.togglePreviousFocus(this._messagesContainerId);

    if (this._isOverlayOpen()) {
      this._closeOverlay();
    } else {
      this._hideMessages();
    }
  }
};

MessagesViewModel.prototype._getNextFocus = function (target) {
  var messageElements = this._getDefaultSlotMessageElements();
  var n = messageElements.indexOf(target);
  var nextFocusMessage;
  if (n - 1 > -1) {
    nextFocusMessage = messageElements[n - 1];
  } else if (n + 1 <= messageElements.length - 1) {
    nextFocusMessage = messageElements[n + 1];
  }

  var nextFocusElement;
  if (nextFocusMessage) {
    nextFocusElement = nextFocusMessage.querySelector('.oj-message-category[tabindex="-1"]');
  }

  return nextFocusElement;
};

MessagesViewModel.prototype._handleAnimateStart = function (event) {
  if (event.defaultPrevented || !this._isEventPertaining(event)) {
    return; // animateStart already overridden on the individual oj-message.
  }

  // eat the event bubbling up from a child oj-message component
  event.preventDefault();
  // event.stopPropagation();

  // override animation
  this._animateMessageAction(event.detail.element, event.detail.action, event.detail.endCallback);
};

MessagesViewModel.prototype._animateMessageAction = function (messageElement, action, endCallback) {
  var display = this._isPresentationInline() ? 'general' : this._computeDisplay();
  var options = this._getThemedAnimateOptions(display, action);

  // oj-messages doesn't publish animateStart/animateEnd so use the simpler syntax for now and allow
  // the event to bubble.
  // oj.AnimationUtils.startAnimation(messageElement, action, options, component).then(endCallback);
  // eslint-disable-next-line no-undef
  AnimationUtils[options.effect](messageElement, options).then(endCallback);
};

MessagesViewModel._DEFAULTS = {
  general: {
    animation: {
      open: { effect: 'expand', duration: '300ms' },
      close: { effect: 'collapse', duration: '300ms' }
    },
    position: {
      my: { horizontal: 'center', vertical: 'top' },
      at: { horizontal: 'center', vertical: 'top' },
      of: 'window',
      collision: 'none'
    }
  },
  notification: {
    animation: {
      open: { effect: 'slideIn', duration: '300ms' },
      close: { effect: 'slideOut', duration: '300ms', direction: 'end' }
    },
    position: {
      my: { horizontal: 'end', vertical: 'top' },
      at: { horizontal: 'end', vertical: 'top' },
      of: 'window',
      collision: 'none'
    }
  }
};

MessagesViewModel.prototype._getThemedAnimateOptions = function (display, action) {
  var themedDefaults = ThemeUtils.parseJSONFromFontFamily('oj-messages-option-defaults');
  if (themedDefaults && themedDefaults[display] && themedDefaults[display].animation &&
      themedDefaults[display].animation[action]) {
    return themedDefaults[display].animation[action];
  }
  return MessagesViewModel._DEFAULTS[display].animation[action];
};

MessagesViewModel.prototype._computeDisplay = function () {
  var display = this._properties.display;
  return display;
};

MessagesViewModel.prototype._isPresentationInline = function () {
  return !this._properties.position;
};

MessagesViewModel.prototype._computeContainerSelectors = function () {
  var display = this._computeDisplay();
  var composite = $(this._composite);

  // something about the layout changed, take this opp. to update classes on the root element
  composite.removeClass('oj-messages-general oj-messages-notification oj-messages-inline');

  if (this._isPresentationInline()) {
    composite.addClass('oj-messages-inline');
  } else {
    composite.addClass(['oj-messages', display].join('-'));
  }

  // a bogus binding on the view, and we return a literal here, but provides a convenient way to be
  //  able to set classes on the root element when the underlying observables change
  return 'oj-messages-container';
};

MessagesViewModel.prototype._getThemedPosition = function () {
  var display = this._computeDisplay();
  var themedDefaults = ThemeUtils.parseJSONFromFontFamily('oj-messages-option-defaults');
  if (themedDefaults[display] && themedDefaults[display].position) {
    return themedDefaults[display].position;
  }
  return MessagesViewModel._DEFAULTS[display].position;
};

MessagesViewModel.prototype._getPositionAsJqUi = function () {
  var position = oj.PositionUtils.coerceToJqUi(this._computePosition());
  var isRtl = oj.DomUtils.getReadingDirection() === 'rtl';
  position = oj.PositionUtils.normalizeHorizontalAlignment(position, isRtl);
  return position;
};

MessagesViewModel.prototype._computePosition = function () {
  var position = this._properties.position;
  return oj.PositionUtils.coerceToJet(position, this._getThemedPosition());
};

MessagesViewModel.prototype._getDefaultSlotMessageElements = function () {
  function toSelector(node) {
    var selector = '';
    if (node) {
      if (node.id && node.id.length > 0) {
        selector = '#' + node.id;
      } else {
        selector = node.nodeName;
        var clazz = node.getAttribute('class');
        if (clazz) {
          selector += '.' + clazz.split(' ').join('.');
        }

        if (node.parentNode) {
          return toSelector(node.parentNode) + ' > ' + selector;
        }
      }
    }

    return selector;
  }

  var element = document.getElementById(this._messagesContainerId);

  var slotMap = oj.BaseCustomElementBridge.getSlotMap(element);
  var messageElements = [];

  // we just need to deal with "default" slot
  var body = slotMap[''];  // default slot
  for (var i = 0; body && i < body.length; i++) {
    // any element child node of oj-messages must be a oj-message, do this validation at times.
    if (body[i].nodeName !== 'OJ-MESSAGE') {
      // we included oj-bind-if in our view def, so make an exception
      if (body[i].nodeName !== 'OJ-BIND-IF') {
        Logger.error(["JET oj-messages id='", toSelector(this._composite),
          "': can contain only oj-message children in its default slot. ",
          "Found a child element id='", toSelector(body[i]), "'."].join(''));
      }
    } else {
      messageElements.push(body[i]);
    }
  }

  return messageElements;
};

MessagesViewModel.prototype._isMessagesShown = function () {
  return $(this._composite).is(':visible');
};

MessagesViewModel.prototype._showMessages = function () {
  if (!this._isMessagesShown()) {
    // unhide the oj-messages root and notify
    $(this._composite).show();
    Components.subtreeShown($(this._composite));
  }

  // When messages are shown in popup, popup.open() will call popup_elem.show(), which is a deep
  //  show() call in its subtree, so behind the scenes, our composite will have 'display:block'
  //  inline style set quietly. For this case _isMessagesShown() check is not reliable. Hence
  //  the accessibility enabling code below is outside of this check, when showing the messages.
  // This is not an issue when hiding the messages though, because the popup effect kicks in later.
  MessagesViewModel.NAVIGATION_TRACKER.add(this._messagesContainerId);
  this._announceNavigation();
};

MessagesViewModel.prototype._hideMessages = function () {
  if (this._isMessagesShown()) {
    // hide the oj-messages node root and notify
    $(this._composite).hide();
    Components.subtreeHidden(this._composite);

    MessagesViewModel.NAVIGATION_TRACKER.remove(this._messagesContainerId);
    if (this._liveRegion) {
      this._liveRegion.destroy();
      delete this._liveRegion;
    }
  }
};

MessagesViewModel.prototype._openOverlay = function () {
  var composite = $(this._composite);
  var psOptions = {};
  psOptions[oj.PopupService.OPTION.POPUP] = composite;
  psOptions[oj.PopupService.OPTION.LAUNCHER] = this._getLauncher();
  psOptions[oj.PopupService.OPTION.POSITION] = this._getPositionAsJqUi();
  psOptions[oj.PopupService.OPTION.EVENTS] = this._getPopupServiceEvents();

  psOptions[oj.PopupService.OPTION.LAYER_SELECTORS] = ['oj', 'messages', 'layer'].join('-');
  psOptions[oj.PopupService.OPTION.MODALITY] = oj.PopupService.MODALITY.MODELESS;
  psOptions[oj.PopupService.OPTION.CUSTOM_ELEMENT] = true;
  oj.PopupService.getInstance().open(psOptions);

  this._showMessages();

  this._overlayEventsCallback = MessagesViewModel._overlayEventsListener.bind(this, composite);

  // snap on tab key handler
  composite[0].addEventListener('keydown', this._overlayEventsCallback, false);
};

MessagesViewModel.prototype._getLauncher = function () {
  var launcher = this._composite.parentElement;

  if (this._composite.previousElementSibling) {
    launcher = this._composite.previousElementSibling;
  } else if (this._composite.nextElementSibling) {
    launcher = this._composite.nextElementSibling;
  }

  return $(launcher);
};

MessagesViewModel.prototype._closeOverlay = function () {
  this._hideMessages();

  var composite = $(this._composite);

  /** @type {!Object.<oj.PopupService.OPTION, ?>} */
  var psOptions = {};
  psOptions[oj.PopupService.OPTION.POPUP] = composite;
  oj.PopupService.getInstance().close(psOptions);

  // remove tab key handler
  var overlayEventsCallback = this._overlayEventsCallback;
  delete this._overlayEventsCallback;
  composite[0].removeEventListener('keydown', overlayEventsCallback, false);
};

MessagesViewModel.prototype._isOverlayOpen = function () {
  var composite = this._composite;
  var status = oj.ZOrderUtils.getStatus(composite);
  return (status === oj.ZOrderUtils.STATUS.OPENING ||
          status === oj.ZOrderUtils.STATUS.OPEN ||
          status === oj.ZOrderUtils.STATUS.CLOSING);
};

MessagesViewModel._overlayEventsListener = function (element, event) {
  if (event.defaultPrevented) {
    return;
  }

  if (event.keyCode === $.ui.keyCode.TAB) {
    var target = event.target;
    var nodes = element.find(':tabbable');
    if (nodes.length > 0) {
      var firstNode = nodes[0];
      var lastNode = nodes[nodes.length - 1];
      if (firstNode === lastNode && target === firstNode) {
        // only one tabstop and key event is on the last/first, eat the event
        event.preventDefault();
      } else if (firstNode === target && event.shiftKey) {
        // tabbing backwards, cycle focus to last node
        event.preventDefault();
        lastNode.focus();  // tabbing backwards, cycle focus to last node
      } else if (lastNode === target && !event.shiftKey) {
        event.preventDefault();
        firstNode.focus(); // tabbing forwards, cycle to the first node
      }
    } else {
      // no tab stops, eat tab event
      event.preventDefault();
    }
  }
};

/**
 * Listens for the ojFocus event fired on the messages container by the navigation
 * tracker. Signals the navigation tracker has forced focus to the messages container
 * in response to F6 keypress from outside the messages container.
 *
 * @private
 * @instance
 * @param {Event|CustomEvent} event
 */
MessagesViewModel.prototype._navigationEventListener = function (event) {
  // The extra id check to to make sure the event is not from ojDialog or ojPopup
  // The dialog and popup raise ojFocus events but the ojFocus raised from the
  // messages navigation tracker is dispatched on the container and doesn't bubble.
  if (event.target.id === this._messagesContainerId) {
    event.preventDefault();
    this._announceNavigation(true);
  }
};

/**
 * Announces F6 navigation commands depending on if focus is within the message region
 * and the platform (mobile, desktop).
 *
 * @private
 * @instance
 * @param {boolean=} isFocusWithin true if focus navigation is to the messages container
 */
MessagesViewModel.prototype._announceNavigation = function (isFocusWithin) {
  var isVOSupported = (oj.AgentUtils.getAgentInfo().os === oj.AgentUtils.OS.IOS ||
                       oj.AgentUtils.getAgentInfo().os === oj.AgentUtils.OS.ANDROID);
  var key;
  if (isFocusWithin) {
    key = isVOSupported ? undefined : 'ariaLiveRegion.navigationFromKeyboard';
  } else {
    key = isVOSupported ? 'ariaLiveRegion.navigationToTouch' : 'ariaLiveRegion.navigationToKeyboard';
  }

  if (key) {
    var liveRegion = this._getLiveRegion();
    var message = this._getTranslationsDefault(key);
    liveRegion.announce(message);
  }
};

MessagesViewModel.prototype._getLiveRegion = function () {
  var id = this._messagesContainerId;
  if (!this._liveRegion) {
    this._liveRegion = new LiveRegion(id);
  }

  return this._liveRegion;
};

MessagesViewModel.prototype._getPopupServiceEvents = function () {
  var events = {};
  events[oj.PopupService.EVENT.POPUP_CLOSE] = this._closeOverlay.bind(this);
  events[oj.PopupService.EVENT.POPUP_REMOVE] = this._surrogateRemoveHandler.bind(this);
  events[oj.PopupService.EVENT.POPUP_REFRESH] = this._refresh.bind(this);

  return events;
};

MessagesViewModel.prototype._refresh = function () {
  if (this._isOverlayOpen()) {
    var messagesBoundingRect = this._composite.getBoundingClientRect();

    // We will reposition only if messages are fully in viewport. Otherwise overflowing messages
    //  cannot be scrolled with page scroll since our popup repositions for scroll events.
    if (messagesBoundingRect.height < document.documentElement.clientHeight &&
        messagesBoundingRect.width < document.documentElement.clientWidth) {
      var position = this._getPositionAsJqUi();
      $(this._composite).position(position);
    }
  }
};

MessagesViewModel.prototype._surrogateRemoveHandler = function () {
  this._closeOverlay();
  this._composite.parentElement.removeChild(this._composite);
};

MessagesViewModel.prototype._createObservables = function () {
  // this can't be obfuscated by the closure compiler
  this.containerSelectors = ko.pureComputed(this._computeContainerSelectors.bind(this), this);
};

MessagesViewModel.prototype._computeLabelLandmark = function () {
  var properties = this._properties;

  if (oj.StringUtils.isEmptyOrUndefined(properties.translations.labelLandmark)) {
    return this._getTranslationsDefault('labelLandmark');
  }

  return properties.translations.labelLandmark;
};

MessagesViewModel.prototype._updateLandmark = function () {
  var labelLandmark = this._computeLabelLandmark();
  this._composite.setAttribute('aria-label', labelLandmark);
  this._composite.setAttribute('role', 'complementary');
};

/**
 * Tracks all visible instances of oj-messages and handles F6 navigation to the the most
 * recently disclosed instance.  In addtion, F6 navigation from the messages back to the
 * last focus element.
 *
 * @const
 * @type {Object}
 */
MessagesViewModel.NAVIGATION_TRACKER = {
  /**
   * @type {Array.<string>}
   */
  _messagesContainerIds: [],
  /**
   * @type {Object}
   */
  _priorFocusCache: {},
  /**
   * @param {string} id
   */
  add: function (id) {
    // adds tracking of the messages container when it becomes disclosed.

    this.remove(id);
    this._messagesContainerIds.push(id);
    this._start(id);
  },
  /**
   * @param {string} id
   */
  remove: function (id) {
    // disabled tracking the message container when it's not disclosed

    var messagesContainerIds = this._messagesContainerIds;
    var i = messagesContainerIds.indexOf(id);
    if (i > -1) {
      messagesContainerIds.splice(i, 1);
    }

    this._stop(id);
  },
  /**
   * @param {string} id
   */
  togglePreviousFocus: function (id) {
    // Restore focus from the messages container to what had focus prior to establishing
    // focus within the messages container. Invoked from press F6 within the messages
    // container.

    var priorFocusCache = this._priorFocusCache;

    /** @type {Element} */
    var target = priorFocusCache[id];

    if (target && $(target).is(':visible') && oj.ZOrderUtils.isAboveTopModalLayer(target)) {
      target.focus();
      delete priorFocusCache[id];
      return true;
    }
    return false;
  },
  /**
   * @param {string} id
   * @param {Element|undefined} target
   */
  _addPriorFocusCache: function (id, target) {
    // Captures the element that had focus prior to navigation to a messages container.
    // navigation can be via F6 or mouse.

    this._priorFocusCache[id] = target;
  },
  /**
   * @param {string} id
   */
  _start: function (id) {
    // Establishes listeners on the message container.  Adds the document listeners if
    // it's the first messages container shown.

    var messagesContainerDiv = document.getElementById(id);
    if (!messagesContainerDiv) {
      return;
    }

    // establish capture focus and keydown bubble listener on the container
    var messageContainerCallback = this._messageContainerListener.bind(this, id);
    messagesContainerDiv.addEventListener('focus', messageContainerCallback, true);
    messagesContainerDiv.addEventListener('keydown', messageContainerCallback, false);
    messagesContainerDiv.addEventListener('click', messageContainerCallback, false);
    $(messagesContainerDiv).data('oj_messages_nmtl', messageContainerCallback);

    // return if already listening
    if (this._documentCallback) {
      return;
    }

    this._documentCallback = this._documentListener.bind(this);
    var docElement = document.documentElement;
    docElement.addEventListener('keydown', this._documentCallback, false);
    docElement.addEventListener('blur', this._documentCallback, true);
  },
  /**
   * @param {string} id
   */
  _stop: function (id) {
    // Removes message container listeners when the messages container is hidden.
    // Hidding the last message container will remove the listeners on the document.

    var messagesContainerDiv = document.getElementById(id);
    if (messagesContainerDiv) {
      /** @type {?} */
      var messageContainerCallback = $(messagesContainerDiv).data('oj_messages_nmtl');
      if (messageContainerCallback) {
        messagesContainerDiv.removeEventListener('focus', messageContainerCallback, true);
        messagesContainerDiv.removeEventListener('keydown', messageContainerCallback, false);
        messagesContainerDiv.removeEventListener('click', messageContainerCallback, false);
      }
    }

    if (!this._documentCallback || this._messagesContainerIds.length > 0) {
      return;
    }

    var docElement = document.documentElement;
    docElement.removeEventListener('keydown', this._documentCallback, false);
    docElement.removeEventListener('blur', this._documentCallback, true);
    delete this._documentCallback;
  },
  /**
   * @param {!Element} target
   * @return {number}
   */
  _indexOfFocusWithin: function (target) {
    // checks if the target of the F6 keydown is within a messages container.
    var messagesContainerIds = this._messagesContainerIds;
    for (var i = 0; i < messagesContainerIds.length; i++) {
      var messagesContainerDiv = document.getElementById(messagesContainerIds[i]);
      if (messagesContainerDiv &&
          oj.DomUtils.isAncestorOrSelf(messagesContainerDiv, target)) {
        return i;
      }
    }

    return -1;
  },
  /**
   * @param {Event} event
   */
  _documentListener: function (event) {
    if (event.defaultPrevented) {
      return;
    }

    // Listeners for F6 navigation to the messages region from any element in the document.  The
    // listener is a bubble keydown.  If the event has been prevented, it's ignored.  If
    // there are multiple oj-messages components in the page, the selection is based on the
    // most recently disclosed order.

    var messagesContainerIds = this._messagesContainerIds;
    // F6 keypress
    if (event.type === 'keydown' && event.keyCode === 117 && messagesContainerIds.length > 0) {
      // Look to see if the event target is already within a message area.  If focus is within
      // do nothing as the F6 listener on the mesage area will handle.
      var s = this._indexOfFocusWithin(event.target);
      if (s > -1) {
        return;  // @see #_messageContainerListener
      }
      s = messagesContainerIds.length - 1;

      // target the most recently disclosed
      for (var i = s; i > -1; i--) {
        var messagesContainerDiv = document.getElementById(messagesContainerIds[i]);

        // if the container doesn't exist or is not visible continue to the next most recently used
        if (messagesContainerDiv && $(messagesContainerDiv).is(':visible') &&
            oj.ZOrderUtils.isAboveTopModalLayer(messagesContainerDiv)) {
          var element = messagesContainerDiv.querySelector('.oj-message-category[tabindex="-1"]');

          event.preventDefault();

          // captrue what had focus prior to pressing F6
          this._addPriorFocusCache(messagesContainerIds[i], event.target);

          element.focus();

          // Use a custom message to signal navigation to the messages container. The event doesn't
          // bubble. The event is used to announce navigation instructions.
          var customEvent = new CustomEvent('ojFocus', { bubbles: false, cancelable: true });
          messagesContainerDiv.dispatchEvent(customEvent);
          break;
        }
      }
    } else if (event.type === 'blur') {
      // capture blur listener used to keep track of what last had focus
      this._prevActiveElement = event.target;
    }
  },
  /**
   * @param {string} id
   * @param {Event} event
   */
  _messageContainerListener: function (id, event) {
    // capture focus and bubble keydown event listener attached to visible message containers

    if (event.defaultPrevented) {
      return;
    }

    if (event.type === 'focus' || event.type === 'click') {
       // if prior focus was outside the messgaes container and new focus is within, capture
       // what had prior focus. This is the best attempt to capture F2 navigation when using
       // mouse or touch to navigate to the message container versus F2 keyboard.

       /** @type {?} */
      var messagesContainerDiv = document.getElementById(id);
      var prevActiveElement = this._prevActiveElement;
      if (prevActiveElement && messagesContainerDiv &&
           !oj.DomUtils.isAncestorOrSelf(messagesContainerDiv, prevActiveElement)) {
        // attached to visible message components.  Tracks previous focus.
        this._addPriorFocusCache(id, prevActiveElement);
      }
    } else if (event.type === 'keydown' &&
            (event.keyCode === 117 || event.keyCode === $.ui.keyCode.ESCAPE)) {
      // F6 or ESC keypress from within the content of the messages container will toggle focus back
      // to to what had prior focus. An attempt is made to fixup the navigate even if arriving
      // using the mouse or touch.
      if (this.togglePreviousFocus(id)) {
        event.preventDefault();
      }
    }
  }
};


/**
 * Utility for handling voice over messages sent to a aria live region.  Messages
 * announced will not be deleted until the live region utility instance is destroyed.
 * They are not cleared due to the chattiness and quick timming messages can be added
 * as it could be too quick to read.
 * @private
 * @constructor
 * @class LiveRegion
 * @ignore
 * @ojtsignore
 * @param {string} id of the messages container
 */
function LiveRegion(id) {
  this.Init(id);
}

/**
 * Adds one to the reference counter instance.
 * @instance
 * @protected
 */
LiveRegion.prototype.Init = function (id) {
  this._id = id;
};

/**
 * Decrements the reference counter destroying the assocaited shared DOM aria
 * live region element when there are no longer any messages using it.
 * @instance
 * @public
 */
LiveRegion.prototype.destroy = function () {
  var liveRegion = $(document.getElementById(LiveRegion._LIVE_REGION_ID));

  // remove all the messages per messages container
  var id = this._id;
  delete this._id;

  liveRegion.find('div[data-container-id="' + id + '"]').remove();

  if (liveRegion.children('div').length < 1) {
    liveRegion.remove();
  }
};

/**
 * Sends a message to the aria live region for voice over mode.
 * @instance
 * @public
 * @param {string} message to be announce in the live region
 */
LiveRegion.prototype.announce = function (message) {
  var liveRegion = LiveRegion._getLiveRegion();
  var id = this._id;
  $('<div>').attr('data-container-id', id).text(message).appendTo(liveRegion);// @HTMLUpdateOK
};


/**
 * Creates or returns an existing aria live region used by messages.
 * @returns {jQuery} aria live region
 * @private
 */
LiveRegion._getLiveRegion = function () {
  var liveRegion = $(document.getElementById(LiveRegion._LIVE_REGION_ID));
  if (liveRegion.length === 0) {
    // only tracks additions
    liveRegion = $('<div>');
    liveRegion.attr({ id: LiveRegion._LIVE_REGION_ID,
      role: 'log',
      'aria-live': 'polite',
      'aria-relevant': 'additions' });
    liveRegion.addClass('oj-helper-hidden-accessible');
    liveRegion.appendTo(document.body);// @HTMLUpdateOK
  }
  return liveRegion;
};

LiveRegion._LIVE_REGION_ID = '__oj_messages_arialiveregion';

/* global __oj_messages_metadata */
// eslint-disable-next-line no-undef
Composite.register('oj-messages',
  {
    view: _MESSAGES_VIEW,
    viewModel: MessagesViewModel,
    metadata: __oj_messages_metadata
  });


});