( function () {
	const gt = mw.guidedTour;

	/**
	 * @class mw.guidedTour.TourBuilder
	 * @classdesc A builder for defining a guided tour
	 *
	 * @constructor
	 * @description Constructs a TourBuilder, backed by a tour, based on an object specifying it.
	 *
	 * The newly constructed object can be further configured with fluent methods,
	 * such as #step
	 * @param {Object} tourSpec Specification of tour
	 * @param {string} tourSpec.name Name of tour; must match module or wiki page name
	 * @param {boolean} [tourSpec.isSinglePage=false] Tour is used on a single
	 *  page tour. This disables tour cookies.
	 * @param {string} [tourSpec.showConditionally] condition for showing
	 *  tour.  Currently, the supported conditions are:
	 *
	 *  - 'stickToFirstPage' - Only show on pages with the same article ID (non-
	 *    special pages) or page name (special pages) as the first page it showed
	 *    on.
	 *  - 'wikitext' - Show on pages that are part of a wikitext flow.  This
	 *    means all pages where the VisualEditor is not open.
	 *  - 'VisualEditor' - Show on pages that are part of the VisualEditor flow.
	 *    This means all pages, except for the wikitext editor, wikitext preview,
	 *    and wikitext show changes.
	 * @param {boolean} [tourSpec.emitTransitionOnStep=false] Whether to emit a
	 *  transition even (meaning transition callbacks are run) when the user clicks
	 *  next or back.
	 * @throws {mw.guidedTour.TourDefinitionError} If tourSpec is missing or the tour
	 *  is unnamed
	 */
	function TourBuilder( tourSpec ) {
		if ( !$.isPlainObject( tourSpec ) || arguments.length !== 1 ) {
			throw new gt.TourDefinitionError( 'Check your syntax. There must be exactly one argument, \'tourSpec\', which must be an object.' );
		}

		if ( typeof tourSpec.name !== 'string' ) {
			throw new gt.TourDefinitionError( '\'tourSpec.name\' must be a string, the tour name.' );
		}

		/**
		 * Tour being built by this TourBuilder
		 *
		 * @property {mw.guidedTour.Tour}
		 * @private
		 */
		this.tour = new gt.Tour( tourSpec );
	}

	/**
	 * Creates and returns a new StepBuilder, used to build a step of the tour.  The
	 * StepBuilder will be bound to the tour being built.
	 *
	 * @param {Object} stepSpec specification for this step of the tour
	 * @param {string} stepSpec.name Step name.  This identifier is not displayed to
	 *   the end user. but can be used as a reference to a step
	 * @param {string} stepSpec.title Title of guider.  Used only
	 *  for on-wiki tours
	 * @param {string} stepSpec.titlemsg Message key for title of
	 *  guider.  Used only for extension-defined tours
	 *
	 * @param {string|mw.guidedTour.WikitextDescription|mw.Title} stepSpec.description
	 *  Description of guider.  A string is treated as HTML, except that for
	 *  backwards compatibility, if onShow is gt.parseDescription or
	 *  gt.getPageAsDescription, the string is interpreted as described in onShow.
	 * @param {string} stepSpec.descriptionmsg Message key for
	 *  description of guider.  Used only for extension-defined tours.
	 *
	 * @param {string|Object} stepSpec.position A positional string specifying
	 *  what part of the element the guider attaches to.  One of 'topLeft',
	 *  'top', 'topRight', 'rightTop', 'right', 'rightBottom', 'bottomRight',
	 *  'bottom', 'bottomLeft', 'leftBottom', 'left', 'leftTop'
	 *
	 *  Or:
	 *
	 *     {
	 *         fallback: 'defaultPosition'
	 *         particularSkin: 'otherPosition',
	 *         anotherSkin: 'anotherPosition'
	 *     }
	 *
	 *  particularSkin should be replaced with a MediaWiki skin name, such as
	 *  monobook.  There can be entries for any number of skins.
	 *  'defaultPosition' is used if there is no custom value for a skin.
	 *
	 *  The position is automatically horizontally flipped if needed (LTR/RTL
	 *  interfaces).
	 *
	 * @param {string|Object|jQuery} stepSpec.attachTo The selector for an element to
	 *  attach to, a jQuery-wrapped node, or an object for that purpose with the same
	 *  format as position.  The values within the structure can also be selectors or
	 *  jQuery-wrapped nodes.
	 *
	 * @param {number} [stepSpec.width=400] Width, in pixels.
	 *
	 * @param {Function} [stepSpec.onShow] Function to execute immediately
	 *  before the guider is shown.  Using this for gt.parseDescription or
	 *  gt.getPageAsDescription is deprecated.  However, a string value of description
	 *  is interpreted as follows:
	 *
	 *  - gt.parseDescription - Treat description as wikitext
	 *  - gt.getPageAsDescription - Treat description as the name of a description
	 *    page on the wiki
	 *
	 * @param {boolean} [stepSpec.allowAutomaticOkay=true] By default, if
	 * you do not specify an Okay or Next button, an Okay button will be generated.
	 *
	 * To suppress this, set allowAutomaticOkay to false for the step.
	 *
	 * @param {boolean} [stepSpec.allowAutomaticNext=true] By default, if
	 * you call .next() and do not specify a Next button a next button will be
	 * generated automatically.
	 *
	 * To suppress this, set allowAutomaticNext to false for the step.
	 *
	 * @param {boolean} [stepSpec.autoFocus=true] By default, the browser will scroll
	 * to make the guider visible.
	 *
	 * To avoid this, set autoFocus to false for the step.
	 *
	 * @param {boolean} [stepSpec.closeOnEscape=true] Close the guider
	 *   when the user presses the Escape key
	 *
	 * @param {boolean} [stepSpec.closeOnClickOutside=true] Close the
	 *  guider when the user clicks elsewhere on screen
	 *
	 * @param {Array} stepSpec.buttons Buttons for step.  See also above
	 *  regarding button behavior and defaults.  Each button can have:
	 *
	 * @param {string} stepSpec.buttons.name Text of button.  Used only
	 *  for on-wiki tours
	 * @param {string} stepSpec.buttons.namemsg Message key for text of
	 *  button.  Used only for extension-defined tours
	 *
	 * @param {Function} stepSpec.buttons.onclick Function to execute
	 *  when button is clicked
	 *
	 * @param {string} stepSpec.buttons.action
	 *  Action keyword.  For actions listed below, you do not need to manually
	 *  specify button name and onclick.
	 *
	 *  Instead, you can pass a defined action as part of the buttons array.  The
	 *  actions currently supported are:
	 *
	 *  - next - Goes to the next step.  Requires mw.guidedTour.StepBuilder#next
	 *    also be called, to specify how the next step is determined.
	 *  - back - Goes back specified step.  Requires mw.guidedTour.StepBuilder#back
	 *    also be called, to specify how the step is determined.
	 *  - okay - An arbitrary function is used for okay button.  This must have
	 *    an accompanying 'onclick':
	 *
	 *        {
	 *            action: 'okay',
	 *            onclick: function () {
	 *                 // Do something...
	 *            }
	 *        }
	 *
	 *  - end - Ends the tour.
	 *  - wikiLink - links to a page on the same wiki
	 *  - externalLink - links to an external page
	 *
	 *  A button action with no parameters looks like:
	 *
	 *     {
	 *         action: 'next'
	 *     }
	 *
	 * Multiple action fields for a single button are not possible.
	 *
	 * @param {string} stepSpec.buttons.page Page to link to, only for
	 *  the wikiLink action
	 * @param {string} stepSpec.buttons.url URL to link to, only for the
	 *  externalLink action
	 * @param {string} stepSpec.buttons.type string to add button type
	 *  class.  Currently supports: progressive, destructive.
	 * @param {string} [stepSpec.buttons.classString] Space-separated list of
	 *  additional class names
	 *
	 *
	 * @return {mw.guidedTour.StepBuilder} Created StepBuilder object
	 * @throws {mw.guidedTour.TourDefinitionError} When the step specification is
	 *  invalid
	 * @memberof mw.guidedTour.TourBuilder
	 * @method step
	 */
	TourBuilder.prototype.step = function ( stepSpec ) {
		if ( this.tour.steps[ stepSpec.name ] ) {
			throw new gt.TourDefinitionError( 'The name "' + stepSpec.name + '" is already taken.  Two steps in a tour can not have the same name.' );
		}

		const stepBuilder = new gt.StepBuilder( this.tour, stepSpec );
		this.tour.steps[ stepSpec.name ] = stepBuilder.step;
		this.tour.stepCount++;
		return stepBuilder;
	};

	/**
	 * Creates and returns a StepBuilder, marking it as the first step (entry point) of
	 * the tour.  This can only be called once.
	 *
	 * @param {Object} stepSpec specification for step; see #step method for details.
	 *
	 * @return {mw.guidedTour.StepBuilder} Created StepBuilder
	 * @throws {mw.guidedTour.TourDefinitionError} When the step specification is
	 *   invalid, or the first step has already been specified
	 * @memberof mw.guidedTour.TourBuilder
	 * @method firstStep
	 */
	TourBuilder.prototype.firstStep = function ( stepSpec ) {
		if ( this.tour.firstStep !== null ) {
			throw new gt.TourDefinitionError( 'You can only specify one first step.' );
		}

		const stepBuilder = this.step( stepSpec );
		this.tour.firstStep = stepBuilder.step;
		return stepBuilder;
	};

	mw.guidedTour.TourBuilder = TourBuilder;
}() );