/*!
 * OpenUI5
 * (c) Copyright 2009-2026 SAP SE or an SAP affiliate company.
 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
 */

sap.ui.define([
	"sap/base/util/deepEqual",
	"sap/base/util/each",
	"sap/ui/base/Object",
	"sap/ui/core/message/Message"
], function(deepEqual, each, BaseObject, Message) {
	"use strict";

	/**
	 * @class
	 * Provides and updates the status data of a binding.
	 * Depending on the model's state and control's state changes, the data state is used to propagate changes to a control.
	 * The control can react to these changes by implementing the <code>refreshDataState</code> method for the control.
	 * Here, the data state object is passed as second parameter.
	 *
	 * Using the {@link #getChanges} method, the control can determine the changed properties and their old and new values.
	 * <pre>
	 *     //sample implementation to handle message changes
	 *     myControl.prototype.refreshDataState = function(oDataState) {
	 *        var aMessages = oDataState.getChanges().messages;
	 *        if (aMessages) {
	 *            for (var i = 0; i &lt; aMessages.length; i++) {
	 *                console.log(aMessages.message);
	 *            }
	 *        }
	 *     }
	 *
	 *     //sample implementation to handle laundering state
	 *     myControl.prototype.refreshDataState = function(oDataState) {
	 *        var bLaundering = oDataState.getChanges().laundering || false;
	 *        this.setBusy(bLaundering);
	 *     }
	 *
	 *     //sample implementation to handle dirty state
	 *     myControl.prototype.refreshDataState = function(oDataState) {
	 *        if (oDataState.isDirty()) console.log("Control " + this.getId() + " is now dirty");
	 *     }
	 * </pre>
	 *
	 * Using the {@link #getProperty} method, the control can read the properties of the data state. The properties are
	 * <ul>
	 *     <li><code>value</code> The value formatted by the formatter of the binding
	 *     <li><code>originalValue</code> The original value of the model formatted by the formatter of the binding
	 *     <li><code>invalidValue</code> The control value that was tried to be applied to the model but was rejected by a type validation
	 *     <li><code>modelMessages</code> The messages that were applied to the binding by the <code>sap.ui.model.MessageModel</code>
	 *     <li><code>controlMessages</code> The messages that were applied due to type validation errors
	 *     <li><code>messages</code> All messages of the data state
	 *      <li><code>dirty</code> true if the value was not yet confirmed by the server
	 * </ul>
	 *
	 * @extends sap.ui.base.Object
	 *
	 * @author SAP SE
	 * @version 1.84.55
	 *
	 * @public
	 * @alias sap.ui.model.DataState
	 */
	var DataState = BaseObject.extend("sap.ui.model.DataState", /** @lends sap.ui.model.DataState.prototype */ {
		metadata : {},
		constructor : function() {
			this.mProperties = {
				modelMessages : [],
				controlMessages: [],
				laundering: false,
				originalValue : undefined,
				originalInternalValue: undefined,
				value : undefined,
				invalidValue: undefined,
				internalValue: undefined,
				dirty: false,
				messages: []

			};
			//the resolved path of the binding to check for binding context changes
			this.mChangedProperties = Object.assign({},this.mProperties);
		}
	});

	/**
	 * Updates the given property with the given value.
	 *
	 * @param {string} sProperty - The property name
	 * @param {any} vValue - The new value
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @private
	 */
	DataState.prototype.setProperty = function(sProperty, vValue) {
		this.mChangedProperties[sProperty] = vValue;
		return this;
	};

	/**
	 * @deprecated Likely unused method
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @private
	 */
	DataState.prototype.calculateChanges = function() {
		for (var sProperty in this.mChangedProperties) {
			var vChangedValue = this.mChangedProperties[sProperty].value;

			if (!deepEqual(this.mProperties[sProperty], vChangedValue)) {
				if (Array.isArray(vChangedValue)) {
					vChangedValue = vChangedValue.slice(0);
				}
				this.mProperties[sProperty] = vChangedValue;
			}
		}

		return this;
	};

	/**
	 * Returns the current value of the property
	 *
	 * @param {string} sProperty - The name of the property
	 * @returns {any} The value of the property
	 * @private
	 */
	DataState.prototype.getProperty = function(sProperty) {
		return this.mChangedProperties[sProperty];
	};

	/**
	 * Returns the array of this data state's messages combining the model and control messages.
	 * The array is sorted descendingly by message severity.
	 *
	 * @returns {sap.ui.core.Message[]} The sorted array of all messages
	 * @public
	 */
	DataState.prototype.getMessages = function () {
		return DataState.getMessagesForProperties(this.mChangedProperties);
	};

	/**
	 * Returns the array of this data state's old messages combining the model and control messages.
	 * The array is sorted descendingly by message severity.
	 *
	 * @returns {sap.ui.core.Message[]} The sorted array of all old messages
	 * @private
	 */
	DataState.prototype._getOldMessages = function() {
		return DataState.getMessagesForProperties(this.mProperties);
	};

	/**
	 * Returns the array of the messages in the given object combining the model and control
	 * messages. The array is sorted descendingly by message severity.
	 *
	 * @param {object} mProperties
	 *   Object with properties <code>controlMessages</code> and <code>modelMessages</code> which
	 *   are both arrays of <code>sap.ui.core.Message</code> objects
	 * @returns {sap.ui.core.Message[]} The sorted array of messages
	 * @private
	 */
	DataState.getMessagesForProperties = function (mProperties) {
		var aMessages = [],
			aControlMessages = mProperties.controlMessages,
			aModelMessages = mProperties.modelMessages;

		if (aModelMessages || aControlMessages) {
			aMessages = aMessages.concat(aModelMessages || [], aControlMessages || []);
			aMessages.sort(Message.compare);
		}
		return aMessages;
	};

	/**
	 * Sets an array of model state messages.
	 *
	 * @param {sap.ui.core.Message[]} aMessages - The model messages for this data state.
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @public
	 */
	DataState.prototype.setModelMessages = function(aMessages) {
		this.mChangedProperties["modelMessages"] = aMessages || [];
		return this;
	};

	/**
	 * Returns the array of state messages of the model or undefined.
	 *
	 * @returns {sap.ui.core.Message[]} The array of messages of the model
	 * @public
	 */
	DataState.prototype.getModelMessages = function() {
		return this.getProperty("modelMessages");
	};

	/**
	 * Sets an array of control state messages.
	 *
	 * @param {sap.ui.core.Message[]} aMessages - The control messages
	 * @return {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @protected
	 */
	DataState.prototype.setControlMessages = function(aMessages) {
		this.mChangedProperties["controlMessages"] = aMessages || [];
		return this;
	};

	/**
	 * Returns the array of state messages of the control.
	 *
	 * @return {sap.ui.core.Message[]} The array of control messages
	 * @public
	 */
	DataState.prototype.getControlMessages = function() {
		return this.getProperty("controlMessages");
	};


	/**
	 * Returns whether the data state is dirty.
	 * A data state is dirty if the value was changed but is not yet confirmed by a server or the
	 * entered value did not yet pass the type validation.
	 *
	 * @returns {boolean} Whether the data state is dirty
	 * @public
	 */
	DataState.prototype.isDirty = function() {
		var vValue = this.mChangedProperties["value"],
			vOriginalValue = this.mChangedProperties["originalValue"];

		return this.isControlDirty() || !deepEqual(vValue, vOriginalValue);
	};

	/**
	 * Returns whether the data state is dirty in the UI control.
	 * A data state is dirty in the UI control if the entered value did not yet pass the type validation.
	 *
	 * @returns {boolean} Whether the data state is dirty
	 * @public
	 */
	DataState.prototype.isControlDirty = function() {
		return this.mChangedProperties["invalidValue"] !== undefined;
	};

	/**
	 * Returns whether the data state is in laundering.
	 * If data is sent to the server, the data state becomes laundering until the
	 * data was accepted or rejected.
	 *
	 * @returns {boolean} Whether the data state is laundering
	 * @public
	 */
	 DataState.prototype.isLaundering = function() {
		return this.mChangedProperties["laundering"];
	};

	/**
	 * Sets the laundering state of the data state.
	 *
	 * @param {boolean} bLaundering Whether the state is laundering
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @protected
	 */
	DataState.prototype.setLaundering = function(bLaundering) {
		this.mChangedProperties["laundering"] = bLaundering;
		return this;
	};

	/**
	 * Returns the formatted value of the data state.
	 *
	 * @returns {any} The value of the data.
	 * @public
	 */
	DataState.prototype.getValue = function() {
		return this.getProperty("value");
	};

	/**
	 * Sets the formatted value of the data state,
	 *
	 * @param {any} vValue the value
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @protected
	 */
	DataState.prototype.setValue = function(vValue) {
		this.mChangedProperties["value"] = vValue;
		return this;
	};

	/**
	 * Returns the dirty value of a binding that was rejected by a type validation so that
	 * it could not be applied to the model. If the
	 * value was not rejected it returns <code>undefined</code>. In this case the current
	 * model value can be accessed using the {@link #getValue} method.
	 *
	 * @returns {any} The value that was rejected or <code>undefined</code>
	 * @public
	 */
	DataState.prototype.getInvalidValue = function() {
		return this.getProperty("invalidValue");
	};

	/**
	 * Sets the dirty value that was rejected by the type validation.
	 *
	 * @param {any} vInvalidValue The value that was rejected by the type validation or
	 *   <code>undefined</code> if the value was valid
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @protected
	 */
	DataState.prototype.setInvalidValue = function(vInvalidValue) {
		this.mChangedProperties["invalidValue"] = vInvalidValue;
		return this;
	};

	/**
	 * Returns the formatted original value of the data.
	 * The original value is the last confirmed value.
	 *
	 * @returns {any} The original confirmed value of the server
	 * @public
	 */
	DataState.prototype.getOriginalValue = function() {
		return this.getProperty("originalValue");
	};

	/**
	 * Sets the formatted original value of the data.
	 *
	 * @param {boolean} vOriginalValue The original value
	 * @returns {sap.ui.model.DataState} <code>this</code> to allow method chaining
	 * @protected
	 */
	DataState.prototype.setOriginalValue = function(vOriginalValue) {
		this.mChangedProperties["originalValue"] = vOriginalValue;
		return this;
	};

	/**
	 * Returns whether the data state is changed, or resets the data state in case the parameter
	 * <code>bNewState</code> is false; reset data state means that the data state properties
	 * are replaced with the changed properties.
	 * As long as there was no call to this method with <code>bNewState</code> set to false, the
	 * data state is dirty, and the corresponding binding will fire data state change events.
	 *
	 * @param {boolean} [bNewState] Whether the data state is to be reset
	 * @returns {boolean} Whether the data state was changed
	 * @protected
	 */
	DataState.prototype.changed = function(bNewState) {
		if (bNewState === false) {
			//clear the changed properties as changed was reset;
			this.mProperties = Object.assign({},this.mChangedProperties);
		}
		return !deepEqual(this.mChangedProperties,this.mProperties);
	};

	/**
	 * Returns the changes of the data state in a map that the control can use in the
	 * <code>refreshDataState</code> method.
	 * The changed property's name is the key in the map. Each element in the map contains an object
	 * with the properties <code>oldValue</code> with the old property value and <code>value</code>
	 * with the new value of the property. The map only contains the changed properties.
	 *
	 * @returns {object} The changed properties of the data state
	 * @public
	 */
	DataState.prototype.getChanges = function() {
		var mChanges = {},
			aMessages,
			aOldMessages;

		each(this.mChangedProperties,function(sProperty, vValue) {
			if (!deepEqual(this.mChangedProperties[sProperty],this.mProperties[sProperty])) {
				mChanges[sProperty] = {};
				mChanges[sProperty].value = this.mChangedProperties[sProperty];
				mChanges[sProperty].oldValue = this.mProperties[sProperty];
			}
		}.bind(this));

		aMessages = this.getMessages();
		aOldMessages = this._getOldMessages();
		if (aMessages.length > 0 || aOldMessages.length > 0) {
			mChanges["messages"] = {};
			mChanges["messages"].oldValue = aOldMessages;
			mChanges["messages"].value = aMessages;
		}

		return mChanges;
	};

	return DataState;
});