const SCHEMA_ID = '/analytics/product_metrics/web/base/1.5.0';
const STREAM_NAME = 'product_metrics.web_base';

const COORDINATOR_XLAB = 'xLab';
const COORDINATOR_FORCED = 'forced';

/**
 * @classdesc This class represents an experiment enrolment for the current user. You can use it to
 *  get which group the user was assigned when they were enrolled into the experiment and send
 *  experiment-related analytics events.
 *
 *  Note well that this class should be constructed using `mw.xLab.getExperiment()` instead, e.g.
 *
 *  ```
 *  const experiment = mw.xLab.getExperiment( 'my-awesome-experiment' );
 *  ```
 * @hideconstructor
 * @package
 */
class Experiment {

	/**
	 * Gets the group assigned to the current user.
	 *
	 * @return {string|null}
	 */
	getAssignedGroup() {
		return this.assignedGroup;
	}

	/**
	 * Gets whether the group assigned to the current user is one of the given groups.
	 *
	 * @see Experiment#getAssignedGroup
	 *
	 * @example
	 * const e = mw.xLab.getExperiment( 'my-awesome-experiment' );
	 *
	 * // Is the current user assigned A or B for the "My Awesome Experiment" experiment?
	 * if ( e.isAssignedGroup( 'A', 'B' ) {
	 *   // ...
	 * }
	 *
	 * @param {...string} groups
	 * @return {boolean}
	 */
	isAssignedGroup( ...groups ) {
		return groups.includes( this.assignedGroup );
	}

	/**
	 * Sends an analytics event related to the experiment.
	 *
	 * If the user is enrolled in the experiment, then the event is decorated with
	 * experiment-related data and sent. The experiment-related data are specified and documented in
	 * [the `fragment/analytics/product_metrics/experiment` schema fragment][0].
	 *
	 * By default, the analytics event will be sent to the `product_metrics.web_base` stream and be
	 * validated with the `/analytics/product_metrics/web/base/1.5.0` schema. The stream and schema
	 * can be overridden with {@link Experiment#setStream} and {@link Experiment#setSchema},
	 * respectively.
	 *
	 * [0]: https://gitlab.wikimedia.org/repos/data-engineering/schemas-event-secondary/-/blob/master/jsonschema/fragment/analytics/product_metrics/experiment/current.yaml?ref_type=heads
	 *
	 * @see mw.eventLog.submitInteraction
	 *
	 * @param {string} action The action that the user enrolled in this experiment took, e.g.
	 *  "hover", "click"
	 * @param {Object} [interactionData] Additional data about the action that the user enrolled in
	 *  the experiment took
	 */
	send( action, interactionData ) {
		const enrollmentDetails = {
			// Fills all the details related to the experiment enrollment
			experiment: {
				enrolled: this.name,
				assigned: this.assignedGroup,
				subject_id: this.subjectId,
				sampling_unit: this.samplingUnit,
				coordinator: this.coordinator
			}
		};
		interactionData = Object.assign( {}, interactionData, enrollmentDetails );

		this.metricsClient.submitInteraction(
			this.streamName,
			this.schemaID,
			action,
			interactionData
		);
	}

	/**
	 * @param {Object} metricsClient
	 * @param {string} name
	 * @param {string|null} [assignedGroup=null]
	 * @param {string|null} [subjectId=null] The subject ID for this experiment
	 * @param {string|null} [samplingUnit=null] The sampling unit for this experiment
	 * @param {string} [coordinator="xLab"] The name of the system that coordinated the enrollment
	 *  of the user into the experiment. This parameter is used as the value for the
	 *  `experiment.coordinator` field on all analytics events sent via {@link Experiment#send} so
	 *  it should be one of `xLab`, `custom`, or `forced`
	 */
	constructor(
		metricsClient,
		name,
		assignedGroup,
		subjectId,
		samplingUnit,
		coordinator
	) {
		this.metricsClient = metricsClient;
		this.name = name;
		this.assignedGroup = assignedGroup || null;
		this.subjectId = subjectId || null;
		this.samplingUnit = samplingUnit || null;
		this.coordinator = coordinator || COORDINATOR_XLAB;
		this.streamName = STREAM_NAME;
		this.schemaID = SCHEMA_ID;
	}

	/**
	 * Submits an event related to this experiment.
	 *
	 * This method makes `Experiment` compatible with [the click-through rate implementation in the
	 * `ext.wikimediaEvents.xLab` ResourceLoader module][0] by proxying to {@link Experiment#send}.
	 * Calling this outside of xLab is not supported.
	 *
	 * [0]: https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/WikimediaEvents/+/master/modules/ext.wikimediaEvents.xLab/ClickThroughRateInstrument.js
	 *
	 * @see https://phabricator.wikimedia.org/T394675
	 *
	 * @package
	 *
	 * @param {string} action The action related to the submitted event
	 * @param {Object} interactionData Additional data
	 */
	submitInteraction( action, interactionData ) {
		this.send( action, interactionData );
	}

	/**
	 * Sets the stream to send analytics events to with {@link Experiment#send}.
	 *
	 * This method is chainable.
	 *
	 * @param {string} streamName
	 * @return {Experiment} The instance on which this method was called
	 */
	setStream( streamName ) {
		if ( !this.metricsClient.getStreamConfig( streamName ) ) {
			// eslint-disable-next-line no-console
			console.warn(
				'%s: The stream %s isn\'t registered. Events will not be sent for this experiment. Have you added %s to $wgMetricsPlatformExperimentStreamNames?',
				this.name,
				streamName,
				streamName
			);
		}

		this.streamName = streamName;

		return this;
	}

	/**
	 * Sets the ID of the schema used to validate analytics events sent with
	 * {@link Experiment#send}.
	 *
	 * This method is chainable.
	 *
	 * @param {string} schemaID
	 * @return {Experiment}
	 */
	setSchema( schemaID ) {
		this.schemaID = schemaID;

		return this;
	}
}

/**
 * @ignore
 */
class UnenrolledExperiment extends Experiment {

	/**
	 * @param {string} name
	 */
	constructor( name ) {
		super( null, name );
	}

	// eslint-disable-next-line no-unused-vars
	send( action, interactionData ) {}

	// eslint-disable-next-line no-unused-vars
	setStream( streamName ) {}
}

/**
 * @ignore
 */
class OverriddenExperiment extends Experiment {

	/**
	 * @param {string} name
	 * @param {string} assignedGroup
	 * @param {string} subjectId
	 * @param {string} samplingUnit
	 */
	constructor(
		name,
		assignedGroup,
		subjectId,
		samplingUnit
	) {
		super(
			null,
			name,
			assignedGroup,
			subjectId,
			samplingUnit,
			COORDINATOR_FORCED
		);
	}

	send( action, interactionData ) {
		// eslint-disable-next-line no-console
		console.log(
			`${ this.name }: The enrolment for this experiment has been overridden. The following event will not be sent:\n`,
			action,
			JSON.stringify( interactionData, null, 2 )
		);
	}

	// eslint-disable-next-line no-unused-vars
	setStream( streamName ) {}
}

module.exports = {
	Experiment,
	UnenrolledExperiment,
	OverriddenExperiment
};