/**
 * Quiz Question
 *
 * @since    3.16.0
 * @version  3.27.0
 */
define( [
		'Models/Image',
		'Collections/Questions',
		'Collections/QuestionChoices',
		'Models/QuestionType',
		'Models/_Relationships',
		'Models/_Utilities'
	], function(
		Image,
		Questions,
		QuestionChoices,
		QuestionType,
		Relationships,
		Utilities
	) {

		return Backbone.Model.extend( _.defaults( {

			/**
			 * Model relationships
			 *
			 * @type  {Object}
			 */
			relationships: {
				parent: {
					model: 'llms_quiz',
					type: 'model',
				},
				children: {
					choices: {
						class: 'QuestionChoices',
						model: 'choice',
						type: 'collection',
					},
					image: {
						class: 'Image',
						model: 'image',
						type: 'model',
					},
					questions: {
						class: 'Questions',
						conditional: function( model ) {
							var type = model.get( 'question_type' ),
							type_id  = _.isString( type ) ? type : type.get( 'id' );
							return ( 'group' === type_id );
						},
						model: 'llms_question',
						type: 'collection',
					},
					question_type: {
						class: 'QuestionType',
						lookup: function( val ) {
							if ( _.isString( val ) ) {
								return window.llms_builder.questions.get( val );
							}
							return val;
						},
						model: 'question_type',
						type: 'model',
					},
				}
			},

			/**
			 * Model defaults
			 *
			 * @return   obj
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			defaults: function() {
				return {
					id: _.uniqueId( 'temp_' ),
					choices: [],
					content: '',
					description_enabled: 'no',
					image: {},
					multi_choices: 'no',
					menu_order: 1,
					points: 1,
					question_type: 'generic',
					questions: [], // for question groups
					parent_id: '',
					title: '',
					type: 'llms_question',
					video_enabled: 'no',
					video_src: '',

					_expanded: false,
				}
			},

			/**
			 * Initializer
			 *
			 * @param    obj   data     object of data for the model
			 * @param    obj   options  additional options
			 * @return   void
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			initialize: function( data, options ) {

				var self = this;

				this.startTracking();
				this.init_relationships( options );

				if ( false !== this.get( 'question_type' ).choices ) {

					this._ensure_min_choices();

					// when a choice is removed, maybe add back some defaults so we always have the minimum
					this.listenTo( this.get( 'choices' ), 'remove', function() {
						// new items are added at index 0 when there's only 1 item in the collection, not sure why exactly...
						setTimeout( function() {
							self._ensure_min_choices();
						}, 0 );
					} );

				}

				// ensure question types that don't support points don't record default 1 point in database
				if ( ! this.get( 'question_type' ).get( 'points' ) ) {
					this.set( 'points', 0 );
				}

				_.delay( function( self ) {
					self.on( 'change:points', self.get_parent().update_points, self.get_parent() );
				}, 1, this );

			},

			/**
			 * Add a new question choice
			 *
			 * @param    obj   data     object of choice data
			 * @param    obj   options  additional options
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			add_choice: function( data, options ) {

				var max = this.get( 'question_type' ).get_max_choices();
				if ( this.get( 'choices' ).size() >= max ) {
					return;
				}

				data    = data || {};
				options = options || {};

				data.choice_type = this.get( 'question_type' ).get_choice_type();
				data.question_id = this.get( 'id' );
				options.parent   = this;

				var choice = this.get( 'choices' ).add( data, options );

				Backbone.pubSub.trigger( 'question-add-choice', choice, this );

			},

			/**
			 * Collapse question_type attribute during full syncs to save to database
			 * Not needed because question types cannot be adjusted after question creation
			 * Called from sync controller
			 *
			 * @param    obj      atts       flat object of attributes to be saved to db
			 * @param    string   sync_type  full or partial
			 *                                 full indicates a force resync or that the model isn't persisted yet
			 * @return   obj
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			before_save: function( atts, sync_type  ) {
				if ( 'full' === sync_type ) {
					atts.question_type = this.get( 'question_type' ).get( 'id' );
				}
				return atts;
			},

			/**
			 * Retrieve the model's parent (if set)
			 *
			 * @return   obj|false
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_parent: function() {

				var rels = this.get_relationships();

				if ( rels.parent ) {
					if ( this.collection && this.collection.parent ) {
						return this.collection.parent;
					} else if ( rels.parent.reference ) {
						return rels.parent.reference;
					}
				}

				return false;

			},

			/**
			 * Retrieve the translated post type name for the model's type
			 *
			 * @param    bool     plural  if true, returns the plural, otherwise returns singular
			 * @return   string
			 * @since    3.27.0
			 * @version  3.27.0
			 */
			get_l10n_type: function( plural ) {

				if ( plural ) {
					return LLMS.l10n.translate( 'questions' );
				}

				return LLMS.l10n.translate( 'question' );
			},

			/**
			 * Gets the index of the question within it's parent
			 * Question numbers skip content elements
			 * & content elements skip questions
			 *
			 * @return   int
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_type_index: function() {

				// current models type, used to check the predicate in the filter function below
				var curr_type = this.get( 'question_type' ).get( 'id' ),
				questions;

				questions = this.collection.filter( function( question ) {

					var type = question.get( 'question_type' ).get( 'id' );

					// if current model is not content, return all non-content questions
					if ( curr_type !== 'content' ) {
						return ( 'content' !== type );
					}

					// current model is content, return only content questions
					return 'content' === type;

				} );

				return questions.indexOf( this );

			},

			/**
			 * Gets iterator for the given type
			 * Questions use numbers and content uses alphabet
			 *
			 * @return   mixed
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_type_iterator: function() {

				var index = this.get_type_index();

				if ( -1 === index ) {
					return '';
				}

				if ( 'content' === this.get( 'question_type' ).get( 'id' ) ) {
					var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split( '' );
					return alphabet[ index ];
				}

				return index + 1;

			},

			get_qid: function() {

				var parent = this.get_parent_question(),
				prefix     = '';

				if ( parent ) {

					prefix = parent.get_qid() + '.';

				}

				// return short_id + this.get_type_iterator();
				return prefix + this.get_type_iterator();

			},

			/**
			 * Retrieve the parent question (if the question is in a question group)
			 *
			 * @return   obj|false
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_parent_question: function() {

				if ( this.is_in_group() ) {

					return this.collection.parent;

				}

				return false;

			},

			/**
			 * Retrieve the parent quiz
			 *
			 * @return   obj
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_parent_quiz: function() {
				return this.get_parent();
			},

			/**
			 * Points getter
			 * ensures that 0 is always returned if the question type doesn't support points
			 *
			 * @return   int
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_points: function() {

				if ( ! this.get( 'question_type' ).get( 'points' ) ) {
					return 0;
				}

				return this.get( 'points' );

			},

			/**
			 * Retrieve the questions percentage value within the quiz
			 *
			 * @return   string
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			get_points_percentage: function() {

				var total = this.get_parent().get( '_points' ),
				points    = this.get( 'points' );

				if ( 0 === total ) {
					return '0%';
				}

				return ( ( points / total ) * 100 ).toFixed( 2 ) + '%';

			},

			/**
			 * Determine if the question belongs to a question group
			 *
			 * @return   {Boolean}
			 * @since    3.16.0
			 * @version  3.16.0
			 */
			is_in_group: function() {

				return ( 'question' === this.collection.parent.get( 'type' ) );

			},

			_ensure_min_choices: function() {

				var choices = this.get( 'choices' );
				while ( choices.size() < this.get( 'question_type' ).get_min_choices() ) {
					this.add_choice();
				}

			},

		}, Relationships, Utilities ) );

} );