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

// Provides control sap.m.DateTimeField.
sap.ui.define([
	'sap/ui/model/type/Date',
	'sap/ui/model/odata/type/ODataType',
	'sap/ui/model/odata/type/DateTimeBase',
	'./InputBase',
	'./ValueStateHeader',
	'sap/ui/core/Core',
	'sap/ui/core/LocaleData',
	'sap/ui/core/library',
	'sap/ui/core/format/DateFormat',
	'./DateTimeFieldRenderer',
	"sap/base/util/deepEqual",
	"sap/base/Log",
	"sap/ui/thirdparty/jquery",
	"sap/ui/core/Configuration",
	// jQuery Plugin "cursorPos"
	"sap/ui/dom/jquery/cursorPos"
], function(
	SimpleDateType,
	ODataType,
	DateTimeBase,
	InputBase,
	ValueStateHeader,
	Core,
	LocaleData,
	coreLibrary,
	DateFormat,
	DateTimeFieldRenderer,
	deepEqual,
	Log,
	jQuery,
	Configuration
) {
	"use strict";

	// shortcut for sap.ui.core.CalendarType
	var CalendarType = coreLibrary.CalendarType;

	// shortcut for sap.ui.core.ValueState
	var ValueState = coreLibrary.ValueState;

	/**
	 * Constructor for a new <code>sap.m.DateTimeField</code>.
	 *
	 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * The <code>sap.m.DateTimeField</code> control provides a basic functionality for date/time input controls.
	 *
	 * To be extended by date and time picker controls. For internal use only.
	 * @abstract
	 *
	 * @extends sap.m.InputBase
	 *
	 * @author SAP SE
	 * @version 1.108.49
	 *
	 * @constructor
	 * @public
	 * @since 1.50.0
	 * @alias sap.m.DateTimeField
	 */
	var DateTimeField = InputBase.extend("sap.m.DateTimeField", /** @lends sap.m.DateTimeField.prototype */ {
		metadata: {
			"abstract" : true,
			library: "sap.m",
			properties: {
				/**
				 * Determines the format, displayed in the input field.
				 */
				displayFormat: {type: "string", group: "Appearance", defaultValue: null},

				/**
				 * Determines the format of the value property.
				 */
				valueFormat: {type: "string", group: "Data", defaultValue: null},

				/**
				 * Holds a reference to a JavaScript Date Object. The <code>value</code> (string)
				 * property will be set according to it. Alternatively, if the <code>value</code>
				 * and <code>valueFormat</code> pair properties are supplied instead,
				 * the <code>dateValue</code> will be instantiated according to the parsed
				 * <code>value</code>.
				 * Use <code>dateValue</code> as a helper property to easily obtain the day, month, year, hours, minutes
				 * and seconds of the chosen date and time. Although possible to bind it, the recommendation is not to do it.
				 * When binding is needed, use <code>value</code> property instead.
				 */
				dateValue: {type: "object", group: "Data", defaultValue: null},

				/**
				 * Holds a reference to a JavaScript Date Object to define the initially focused
				 * date/time when the picker popup is opened.
				 *
				 * <b>Notes:</b>
				 * <ul>
				 * <li>Setting this property does not change the <code>value</code> property.</li>
				 * <li>Depending on the context this property is used in ({@link sap.m.TimePicker},
				 * {@link sap.m.DatePicker} or {@link sap.m.DateTimePicker}), it takes into account only the time part, only
				 * the date part or both parts of the JavaScript Date Object.</li>
				 * </ul>
				 * @since 1.54
				 */
				initialFocusedDateValue: {type: "object", group: "Data", defaultValue: null}
			},
			events : {

				/**
				 * Fired when the value of the <code>DateTimeField</code> is changed by user interaction - each keystroke, delete, paste, etc.
				 *
				 * <b>Note:</b> Browsing autocomplete suggestions doesn't fire the event.
				 * @since 1.104.0
				 */
				liveChange: {
					parameters : {
						/**
						 * The current value of the input, after a live change event.
						 */
						value: {type : "string"},

						/**
						 * The previous value of the input, before the last user interaction.
						 */
						previousValue: {type : "string"}
					}
				}
			}
		},

		renderer: DateTimeFieldRenderer
	});

	DateTimeField.prototype.setValue = function (sValue) {

		sValue = this.validateProperty("value", sValue); // to convert null and undefined to ""

		var sOldValue = this.getValue();
		if (sValue === sOldValue) {
			return this;
		} else {
			this.setLastValue(sValue);
		}

		// convert to date object and check validity on output
		var oDate = this._parseAndValidateValue(sValue);
		this.setProperty("dateValue", oDate, this._bPreferUserInteraction);

		// do not call InputBase.setValue because the displayed value and the output value might have different pattern
		this._formatValueAndUpdateOutput(oDate, sValue);
		this.setProperty("value", sValue, this._bPreferUserInteraction);

		return this;
	};

	DateTimeField.prototype._parseAndValidateValue = function(sValue) {
		this._bValid = true;

		// convert to date object
		var oDate;
		if (sValue) {
			try {
				oDate = this._parseValue(sValue);
			} catch (e) {
				//ignore parsing error
			}

			if (Array.isArray(oDate)) {
				oDate = oDate[0];
			}

			if (!oDate || !oDate.getTime || oDate.getTime() < this._oMinDate.getTime() || oDate.getTime() > this._oMaxDate.getTime()) {
				this._bValid = false;
				Log.warning("Value can not be converted to a valid date", this);
			}
		}

		return oDate;
	};

	DateTimeField.prototype._formatValueAndUpdateOutput = function(oDate, sValue) {
		if (!this.getDomRef()) {
			return;
		}
		// convert to output
		var sOutputValue = oDate ? this._formatValue(oDate) : sValue;

		if (this._bPreferUserInteraction) {
			// Handle the value concurrency before setting the value property of the control,
			// in order to distinguish whether the user only focused the input field or typed in it
			this.handleInputValueConcurrency(sOutputValue);
		} else if (this._$input.val() !== sOutputValue) {
			// update the DOM value when necessary
			// otherwise cursor can go to the end of text unnecessarily
			this._$input.val(sOutputValue);
			this._curpos = this._$input.cursorPos();
		}
	};

	DateTimeField.prototype.setDateValue = function (oDate) {

		if (!this._isValidDate(oDate)) {
			throw new Error("Date must be a JavaScript date object; " + this);
		}

		if (deepEqual(this.getDateValue(), oDate)) {
			return this;
		}

		oDate = this._dateValidation(oDate);

		// convert date object to value
		var sValue = this._formatValue(oDate, true);

		if (sValue !== this.getValue()) {
			this.setLastValue(sValue);
		}
		// set the property in any case but check validity on output
		this.setProperty("value", sValue);

		if (this.getDomRef()) {
			// convert to output
			var sOutputValue = this._formatValue(oDate);

			if (this._$input.val() !== sOutputValue) {
				this._$input.val(sOutputValue);
				this._curpos = this._$input.cursorPos();
			}
		}

		return this;
	};

	DateTimeField.prototype.setValueFormat = function (sValueFormat) {
		// if valueFormat changes the value must be parsed again
		this.setProperty("valueFormat", sValueFormat, true); // no rerendering

		var sValue = this.getValue();

		if (sValue) {
			this._handleDateValidation(this._parseValue(sValue));
		}

		return this;
	};

	DateTimeField.prototype.setDisplayFormat = function (sDisplayFormat) {

		this.setProperty("displayFormat", sDisplayFormat, true); // no rerendering

		this.updateDomValue(this._formatValue(this.getDateValue()));

		this.setPlaceholder(this._getPlaceholder());

		return this;
	};

	DateTimeField.prototype.getDisplayFormatType = function () {
		return null;
	};

	DateTimeField.prototype.onfocusin = function(oEvent) {

		if (!jQuery(oEvent.target).hasClass("sapUiIcon")) {
			this.addStyleClass("sapMFocus");
		}

		if (!jQuery(oEvent.target).hasClass("sapMInputBaseIconContainer") && !(this._oPopup && this._oPopup.isOpen())) {
			// open value state message popup when focus is in the input
			this.openValueStateMessage();
		} else if (this._oValueStateHeader) {
			this._oValueStateHeader
				.setValueState(this.getValueState())
				.setText(this._getTextForPickerValueStateContent())
				.setVisible(this.getValueState() !== ValueState.None);
		}

		this._sPreviousValue = this.getDOMValue();
	};

	/**
	 * Event handler for user input.
	 *
	 * @public
	 * @param {jQuery.Event} oEvent User input.
	 */
	 DateTimeField.prototype.oninput = function(oEvent) {
		InputBase.prototype.oninput.call(this, oEvent);
		if (oEvent.isMarked("invalid")) {
			return;
		}

		var sValue = this.getDOMValue();

		if (sValue !== this._sPreviousValue) {
			this.fireLiveChange({
				value: sValue,
				previousValue : this._sPreviousValue
			});
			this._sPreviousValue = sValue;
		}
	 };

	/**
	 * Gets the inner input DOM value.
	 *
	 * @protected
	 * @returns {any} The value of the input.
	 */
	 DateTimeField.prototype.getDOMValue = function() {
		return this._$input.val();
	};

	DateTimeField.prototype._getValueStateHeader = function () {
		var sValueState;

		if (!this._oValueStateHeader) {
			sValueState = this.getValueState();

			this._oValueStateHeader = new ValueStateHeader({
				text: this._getTextForPickerValueStateContent(),
				valueState: sValueState,
				visible: sValueState !== ValueState.None
			});
		}

		return this._oValueStateHeader;
	};

	DateTimeField.prototype._dateValidation = function (oDate) {
		this._bValid = true;
		this.setProperty("dateValue", oDate);

		return oDate;
	};

	DateTimeField.prototype._handleDateValidation = function (oDate) {
		this._bValid = true;
		this.setProperty("dateValue", oDate);
	};

	DateTimeField.prototype._getPlaceholder = function() {

		var sPlaceholder = this.getPlaceholder(),
			oBinding = this.getBinding("value"),
			oBindingType = oBinding && oBinding.getType && oBinding.getType();

		if (!sPlaceholder) {
			if (oBindingType instanceof SimpleDateType) {
				return oBindingType.getPlaceholderText();
			}

			if (oBindingType instanceof ODataType && oBindingType.oFormat) {
				return oBindingType.oFormat.getPlaceholderText();
			}

			// always get placeholder from the display format
			sPlaceholder = this._getFormatter(true).getPlaceholderText();
		}

		return sPlaceholder;

	};

	DateTimeField.prototype._getLocaleBasedPattern = function (sPlaceholder) {
		return LocaleData.getInstance(
			Configuration.getFormatSettings().getFormatLocale()
		).getDatePattern(sPlaceholder);
	};

	DateTimeField.prototype._getTimezoneFormatter = function() {
		if (!this._timezoneFormatter) {
			this._timezoneFormatter = DateFormat.getDateTimeWithTimezoneInstance({ showTimezone: false });
		}

		return this._timezoneFormatter;
	};

	DateTimeField.prototype._parseValue = function (sValue, bDisplayFormat) {
		var oBinding = this.getBinding("value"),
			oBindingType = oBinding && oBinding.getType && oBinding.getType(),
			// The internal "_getFormatter" method gets called now if there is a binding to the "value" property with
			// a supported binding type. As a result all needed internal control variables are created.
			oFormatter = this._getFormatter(bDisplayFormat),
			oFormatOptions,
			oDateLocal,
			oDate,
			sFormatted;

		if (this._isSupportedBindingType(oBindingType)) {
			try {
				oDate = oBindingType.parseValue(sValue, "string");

				if (typeof (oDate) === "string" && oBindingType instanceof DateTimeBase) {
					oDate = DateTimeBase.prototype.parseValue.call(oBindingType, sValue, "string");
				}

				oFormatOptions = oBindingType.oFormatOptions;
				if (oFormatOptions && oFormatOptions.source && oFormatOptions.source.pattern == "timestamp") {
					// convert timestamp back to Date
					oDate = new Date(oDate);
				} else if (oFormatOptions && oFormatOptions.source && typeof oFormatOptions.source.pattern === "string") {
					oDate = oBindingType.oInputFormat.parse(sValue);
				}
			} catch (e) {
				// ignore, ParseException to be handled in ManagedObject.updateModelProperty()
			}

			if (oDate && ((oBindingType.oFormatOptions && this._isFormatOptionsUTC(oBindingType.oFormatOptions)) || (oBindingType.oConstraints && oBindingType.oConstraints.isDateOnly))) {
				// convert to local date because it was parsed as UTC date
				sFormatted = this._getTimezoneFormatter().format(oDate, "UTC");
				oDateLocal = this._getTimezoneFormatter().parse(sFormatted,
					Configuration.getTimezone())[0];

				oDate = oDateLocal;
			}
			return oDate;
		}

		return oFormatter.parse(sValue);
	};

	/* The bValueFormat variable defines whether the result is in valueFormat(true) or displayFormat(false) */
	DateTimeField.prototype._formatValue = function (oDate, bValueFormat) {
		if (!oDate) {
			return "";
		}

		var oBinding = this.getBinding("value"),
			oBindingType = oBinding && oBinding.getType && oBinding.getType(),
			oFormatOptions,
			oDateUTC,
			sFormatted;

		if (this._isSupportedBindingType(oBindingType)) {
			if ((oBindingType.oFormatOptions && oBindingType.oFormatOptions.UTC) || (oBindingType.oConstraints && oBindingType.oConstraints.isDateOnly)) {
				// convert to UTC date because it will be formatted as UTC date
				sFormatted = this._getTimezoneFormatter().format(oDate, Configuration.getTimezone());
				oDateUTC = this._getTimezoneFormatter().parse(sFormatted, "UTC")[ 0 ];

				oDate = oDateUTC;
			}

			oFormatOptions = oBindingType.oFormatOptions;
			if (oFormatOptions && oFormatOptions.source && oFormatOptions.source.pattern == "timestamp") {
				// convert Date to timestamp
				oDate = oDate.getTime();
			} else if (oBindingType.oOutputFormat) {
				return oBindingType.oOutputFormat.format(oDate);
			}

			return oBindingType.formatValue(oDate, "string");
		}

		/* The logic of _getFormatter function expects the opposite boolean variable of bValueFormat */
		return this._getFormatter(!bValueFormat).format(oDate);
	};

	DateTimeField.prototype._isSupportedBindingType = function (oBindingType) {
		return !!oBindingType && oBindingType.isA([
			"sap.ui.model.type.Date",
			"sap.ui.model.odata.type.DateTime",
			"sap.ui.model.odata.type.DateTimeOffset"
		]);
	};

	DateTimeField.prototype._isFormatOptionsUTC = function (oBindingTypeFormatOptions) {
		// UTC can be set directly in oFormatOptions or inside the source along with the pattern
		return (oBindingTypeFormatOptions.UTC || (oBindingTypeFormatOptions.source && oBindingTypeFormatOptions.source.UTC));
	};

	DateTimeField.prototype._getDefaultDisplayStyle = function () {
		return "medium";
	};

	DateTimeField.prototype._getDefaultValueStyle = function () {
		return "short";
	};

	/* The bDisplayFormat variable defines whether the result is in displayFormat(true) or valueFormat(false) */
	DateTimeField.prototype._getFormatter = function (bDisplayFormat) {
		var sPattern = this._getBoundValueTypePattern(),
			bRelative = false, // if true strings like "Tomorrow" are parsed fine
			oFormat,
			oBinding = this.getBinding("value"),
			sCalendarType;

		if (oBinding && oBinding.oType && oBinding.oType.oOutputFormat) {
			bRelative = !!oBinding.oType.oOutputFormat.oFormatOptions.relative;
			sCalendarType = oBinding.oType.oOutputFormat.oFormatOptions.calendarType;
		}

		/* eslint-disable no-lonely-if */
		if (!sPattern) {
			// not databinding is used -> use given format
			if (bDisplayFormat) {
				sPattern = ( this.getDisplayFormat() || this._getDefaultDisplayStyle() );
				sCalendarType = this.getDisplayFormatType();
			} else {
				sPattern = ( this.getValueFormat() || this._getDefaultValueStyle() );
				sCalendarType = CalendarType.Gregorian;
			}
		}

		if (!sCalendarType) {
			sCalendarType = Configuration.getCalendarType();
		}

		if (bDisplayFormat) {
			if (sPattern === this._sUsedDisplayPattern && sCalendarType === this._sUsedDisplayCalendarType) {
				oFormat = this._oDisplayFormat;
			}
		} else {
			if (sPattern === this._sUsedValuePattern && sCalendarType === this._sUsedValueCalendarType) {
				oFormat = this._oValueFormat;
			}
		}

		if (oFormat) {
			return oFormat;
		}

		return this._getFormatterInstance(oFormat, sPattern, bRelative, sCalendarType, bDisplayFormat);
	};

	DateTimeField.prototype._getFormatterInstance = function (oFormat, sPattern, bRelative, sCalendarType, bDisplayFormat) {

		if (this._checkStyle(sPattern)) {
			oFormat = this._getFormatInstance({style: sPattern, strictParsing: true, relative: bRelative, calendarType: sCalendarType}, bDisplayFormat);
		} else {
			oFormat = this._getFormatInstance({pattern: sPattern, strictParsing: true, relative: bRelative, calendarType: sCalendarType}, bDisplayFormat);
		}

		if (bDisplayFormat) {
			this._sUsedDisplayPattern = sPattern;
			this._sUsedDisplayCalendarType = sCalendarType;
			this._oDisplayFormat = oFormat;
		} else {
			this._sUsedValuePattern = sPattern;
			this._sUsedValueCalendarType = sCalendarType;
			this._oValueFormat = oFormat;
		}

		return oFormat;
	};

	DateTimeField.prototype._getFormatInstance = function (oArguments, bDisplayFormat) {
		return DateFormat.getInstance(oArguments);
	};

	DateTimeField.prototype._checkStyle = function (sPattern) {
		return (sPattern === "short" || sPattern === "medium" || sPattern === "long" || sPattern === "full");
	};

	DateTimeField.prototype._getDisplayFormatPattern = function () {
		var sPattern = this._getBoundValueTypePattern();

		if (sPattern) {
			return sPattern;
		}

		sPattern = this.getDisplayFormat();

		if (this._checkStyle(sPattern)) {
			sPattern = this._getLocaleBasedPattern(sPattern);
		}

		return sPattern;
	};

	DateTimeField.prototype._getBoundValueTypePattern = function () {
		var oBinding = this.getBinding("value"),
			oBindingType = oBinding && oBinding.getType && oBinding.getType();

		if (oBindingType instanceof SimpleDateType) {
			return oBindingType.getOutputPattern();
		}

		if (oBindingType instanceof ODataType && oBindingType.oFormat) {
			return oBindingType.oFormat.oFormatOptions.pattern;
		}

		return undefined;
	};

	// Cross frame check for a date should be performed here otherwise setDateValue would fail in OPA tests
	// because Date object in the test is different than the Date object in the application (due to the iframe).
	DateTimeField.prototype._isValidDate = function (oDate) {
		return !oDate || Object.prototype.toString.call(oDate) === "[object Date]";
	};

	/**
	 * Gets the text for the picker's subheader title.
	 * In case <code>valueStateText</code> is not set, a default value is returned.
	 * @returns {string}
	 * @private
	 */
	DateTimeField.prototype._getTextForPickerValueStateContent = function() {
		return this.getValueStateText() || this._getDefaultTextForPickerValueStateContent();
	};

	/**
	 * Gets the default text for the picker's subheader title.
	 * @returns {string}
	 * @private
	 */
	DateTimeField.prototype._getDefaultTextForPickerValueStateContent = function() {
		var sValueState = this.getValueState(),
			oResourceBundle,
			sText;

		if (sValueState === ValueState.None) {
			sText = "";
		} else {
			oResourceBundle = Core.getLibraryResourceBundle("sap.ui.core");
			sText = oResourceBundle.getText("VALUE_STATE_" + sValueState.toUpperCase());
		}

		return sText;
	};

	return DateTimeField;

});