/*!
 * 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/deepExtend",
	"sap/ui/base/Object"
], function(deepExtend, BaseObject) {
	"use strict";

	/**
	 * Meant to be implemented/overridden by a child object. Specifically, the functions "init" and "closeApplication"
	 * need to be overridden.
	 *
	 * @abstract
	 * @class
	 * Provides the interface between human and machine since a Gherkin feature file is human-readable
	 * and the computer does not know how to execute its steps.
	 *
	 * It defines what each step in the Gherkin feature file will actually do when it is executed.
	 * @author Rodrigo Jordao
	 * @author Jonathan Benn
	 * @extends sap.ui.base.Object
	 * @alias sap.ui.test.gherkin.StepDefinitions
	 * @since 1.40
	 * @public
	 */
	var StepDefinitions = BaseObject.extend("sap.ui.test.gherkin.StepDefinitions",
			/** @lends sap.ui.test.gherkin.StepDefinitions.prototype */ {

			constructor : function() {
				BaseObject.apply(this, arguments);

				/**
				 * An array of StepDefinition objects, one of which is added to the array every time
				 * the user calls the "register" method. Each StepDefinition object can generate one TestStep object.
				 *
				 * @type {StepDefinition[]}
				 * @see #register
				 * @see #_generateTestStep
				 * @private
				 */
				this._aDefinitions = [];
				this.init();
			},

			/**
			 * Registers the step definitions by calling the method "register".
			 *
			 * @see #register
			 * @abstract
			 * @public
			 * @function
			 * @static
			 */
			init : function() {},

			/**
			 * Closes the application and cleans up any mess made by the tests. To avoid erroneous exceptions during test
			 * execution, make sure that it is safe to run this method even if the application was never started.
			 *
			 * @abstract
			 * @public
			 */
			closeApplication: function() {},

			/**
			 * Registers a step definition.
			 *
			 * @param {RegExp} rRegex
			 *     The regular expression that matches the feature file step (with leading "Given", "When",
			 *     "Then", "But" or "*" removed). E.g. if the feature file has the step
			 *     "Then I should be served a coffee" it will be truncated to "I should be served a coffee"
			 *     and tested against "rRegex" to check for a match. The simple regular expression
			 *     /^I should be served a coffee$/i would match this text. The regular
			 *     expression can specify capturing groups, which will be passed as parameters to "fnFunc".
			 * @param {function} fnFunc
			 *     the function to execute in the event that the regular expression matches. Receives
			 *     regular expression capturing groups as parameters in the same order that they are
			 *     specified in the regular expression. If a data table is specified for the step, it
			 *     will be passed as an additional final parameter. At execution time, all functions
			 *     within a particular scenario will execute within the same "this" context.
			 * @throws {Error} if any parameters are invalid, or if method is called twice with the same value for 'rRegex'
			 * @public
			 */
			register : function(rRegex, fnFunc) {
				if (!(rRegex instanceof RegExp)) {
					throw new Error("StepDefinitions.register: parameter 'rRegex' must be a valid RegExp object");
				}
				if (typeof fnFunc !== "function") {
					throw new Error("StepDefinitions.register: parameter 'fnFunc' must be a valid Function");
				}

				this._aDefinitions.forEach(function(oStepDef) {
					if (oStepDef.rRegex.source === rRegex.source) {
						throw new Error("StepDefinitions.register: Duplicate step definition '" + rRegex + "'");
					}
				});

				this._aDefinitions.push({
					rRegex: rRegex,
					generateTestStep: function(oStep) {
						var aMatch = oStep.text.match(rRegex);
						if (!aMatch) { return {isMatch: false}; }
						var aParams = aMatch.slice(1);
						if (oStep.data) { aParams.push(deepExtend([], oStep.data)); }
						return {
							isMatch: true,
							text: oStep.text,
							regex: rRegex,
							parameters: aParams,
							func: fnFunc
						};
					}
				});
			},

			/**
			 * Searches through the registered step definitions, finds the one that matches the given feature file step and
			 * generates a new TestStep object that combines the two.
			 *
			 * @param {object} oStep - a feature file step, optionally with an associated data table
			 * @param {string} oStep.text - the feature file step's human-readable text
			 * @returns {TestStep} a TestStep object
			 * @see sap.ui.test.gherkin.GherkinTestGenerator
			 * @private
			 */
			_generateTestStep : function(oStep) {

				var aMatchingTestSteps = [];

				// for each registered step definition
				this._aDefinitions.forEach(function(oDefinition) {
					// check if the inputed Feature file step matches the registered step definition
					var oTestStep = oDefinition.generateTestStep(oStep);
					if (oTestStep.isMatch) {
						aMatchingTestSteps.push(oTestStep);
					}
				});

				// if a unique match was made
				if (aMatchingTestSteps.length === 1) {
					return aMatchingTestSteps[0];

				// if an ambiguous match was made
				} else if (aMatchingTestSteps.length > 1) {

					var sListOfHumanReadableRegexes = aMatchingTestSteps
						.map(function(i){return "'" + i.regex + "'";}) // e.g. ['/regex1/', '/regex2/', '/regex3/']
						.join(', ')                                    // e.g. "'/regex1/', '/regex2/', '/regex3/'"
						.replace(/,([^,]*)$/, ' and$1');               // e.g. "'/regex1/', '/regex2/' and '/regex3/'"

					throw new Error( "Ambiguous step definition error: " + aMatchingTestSteps.length + " step definitions " +
						sListOfHumanReadableRegexes + " match the feature file step '" + oStep.text + "'");
				}
				// else if no matches were made
				return {
					isMatch: false,
					text: "(NOT FOUND) " + oStep.text
				};
			}

		});

	return StepDefinitions;

});
