window.fwExtBuilderInitialize = (function ($) {
	var fixedHeaderHelpers = {
		increment: 0,
		$adminBar: $('#wpadminbar'),
		getAdminBarHeight: function() {
			if (this.$adminBar.length && this.$adminBar.css('position') === 'fixed') {
				return this.$adminBar.height();
			} else {
				return 0;
			}
		},
		fix: function($header, $builder, $scrollParent){
			var topSpace = this.getAdminBarHeight(),
				scrollParentHeight = $scrollParent.height(),
				scrollParentScrollTop = $scrollParent.scrollTop(),
				scrollParentOffset = $scrollParent.offset(),
				builderHeight = $builder.get(0).clientHeight,
				builderOffsetTop = $builder.offset().top,
				headerHeight = $header.get(0).clientHeight;

			/**
			 * Fixes inside options modal
			 */
			if (scrollParentOffset) {
				builderOffsetTop -= scrollParentOffset.top;

				if (builderOffsetTop <= 0) {
					builderOffsetTop = topSpace;
				}
			}

			do {
				if (builderOffsetTop >= scrollParentScrollTop + topSpace) {
					// scroll top didn't reached the builder
					break;
				}

				if (builderHeight < scrollParentHeight - topSpace) {
					// the builder fits inside the scroll element
					break;
				}

				var bottomLimit = Math.floor(scrollParentHeight / 2);
				if (bottomLimit < headerHeight) {
					bottomLimit = headerHeight;
				}
				if (bottomLimit < 256) {
					bottomLimit = 256;
				}

				if (builderHeight < headerHeight + bottomLimit) {
					break;
				}

				if (scrollParentHeight < headerHeight + bottomLimit) {
					// the scroll element must have space to display header and bottomLimit
					break;
				}

				var headerTopShift = (scrollParentScrollTop + topSpace) - builderOffsetTop;

				if (headerTopShift + headerHeight + bottomLimit > builderHeight) {
					// do not allow header to cover last items
					headerTopShift -= headerTopShift + headerHeight + bottomLimit - builderHeight;
				}

				// set fixed header
				{
					if (!$builder.hasClass('fixed-header')) {
						$builder.addClass('fixed-header');
					}

					$builder.css({
						'padding-top': headerHeight +'px' // set ghost space in builder, like the header is still there
					});
					$header.css({
						'top': headerTopShift +'px'
					});

					return true;
				}
			} while(false);

			// remove fixed header
			{
				if ($builder.hasClass('fixed-header')) {
					$builder.removeClass('fixed-header');

					$builder.css({
						'padding-top': ''
					});
					$header.css({
						'top': ''
					});
				}

				return false;
			}
		}
	};

	return {
		init: init
	};

	/**
	 * Loop recursive through all items in given collection
	 */
	function forEachItemRecursive(collection, callback) {
		collection.each(function(item){
			callback(item);

			forEachItemRecursive(item.get('_items'), callback);
		});
	}

	function initDraggable ($this, builder, id) {
		fwEvents.trigger('fw:options:init:tabs', {$elements: $this.find('> .builder-items-types')});

		$this.find('> .builder-items-types .builder-item-type').draggable({
			connectToSortable: '#'+ id +' .builder-root-items .builder-items',
			helper: 'clone',
			distance: 10,
			placeholder: 'fw-builder-placeholder',
			zIndex: 99999,
			start: function(event, ui) {
				var movedType = ui.helper.attr('data-builder-item-type');

				if (!movedType) {
					return;
				}

				var MovedTypeClass = builder.getRegisteredItemClassByType(movedType);

				if (!MovedTypeClass) {
					return;
				}

				/**
				 * add "allowed" classes to items vies where allowIncomingType(movedType) returned true
				 * else add "denied" class
				 */
				{
					{
						if (MovedTypeClass.prototype.allowDestinationType(null)) {
							builder.rootItems.view.$el.addClass('fw-builder-item-allow-incoming-type');
						} else {
							builder.rootItems.view.$el.addClass('fw-builder-item-deny-incoming-type');
						}
					}

					forEachItemRecursive(builder.rootItems, function(item){
						if (
							item.allowIncomingType(movedType)
							&&
							MovedTypeClass.prototype.allowDestinationType(item.get('type'))
						) {
							item.view.$el.addClass('fw-builder-item-allow-incoming-type');
						} else {
							item.view.$el.addClass('fw-builder-item-deny-incoming-type');
						}
					});
				}
			},
			stop: function() {
				// remove "allowed" and "denied" classes from all items
				{
					builder.rootItems.view.$el.removeClass(
						'fw-builder-item-allow-incoming-type fw-builder-item-deny-incoming-type'
					);

					forEachItemRecursive(builder.rootItems, function(item){
						item.view.$el.removeClass(
							'fw-builder-item-allow-incoming-type fw-builder-item-deny-incoming-type'
						);
					});
				}
			}
		});

		/**
		 * Add item on thumbnail click
		 */
		$this.find('.builder-items-types').on('click', '.builder-item-type', function(){
			var $itemType = $(this);

			var itemType = $itemType.attr('data-builder-item-type');

			if (itemType) {
				var ItemTypeClass = builder.getRegisteredItemClassByType(itemType);

				if (ItemTypeClass) {
					if (ItemTypeClass.prototype.allowDestinationType(null)) {
						builder.rootItems.add(
							new ItemTypeClass({}, {
								$thumb: $itemType
							})
						);

						// animation
						{
							// stop previous animation
							{
								clearTimeout($itemType.attr('data-animation-timeout-id'));
								$itemType.removeClass('fw-builder-animation-item-type-add');
							}

							$itemType.addClass('fw-builder-animation-item-type-add');

							$itemType.attr('data-animation-timeout-id',
								setTimeout(function(){
									$itemType.removeClass('fw-builder-animation-item-type-add');
								}, 500)
							);
						}

						// scroll to the bottom of the builder
						setTimeout(function(){
							var $builderOption = $this,
								$scrollParent = $builderOption.scrollParent();

							if ($scrollParent.get(0) === document || $scrollParent.get(0) === document.body) {
								$scrollParent = $(window);
							}

							if ($builderOption.height() <= $scrollParent.height() + 300) {
								/**
								 * Do not scroll if the builder can fit or is almost entirely visible
								 * To prevent "jumping" https://github.com/ThemeFuse/Unyson/issues/815
								 */
								return;
							}

							$scrollParent.scrollTop(
								$builderOption.offset().top
								+
								$builderOption.outerHeight()
								-
								$scrollParent.height()
							);
						}, 0);
					} else {
						console.warn('Item type "'+ itemType +'" is not allowed as first level item');
					}
				} else {
					console.error('Unregistered item type: '+ itemType);
				}
			} else {
				console.error('Cannot extract item type from element', $itemType);
			}
		});
	}

	function init (Builder) {
		fwEvents.on('fw:options:init', function (data) {
			var $options = data.$elements.find('.fw-option-type-builder:not(.initialized)');

			if (!$options.length) {
				return;
			}

			$options.closest('.fw-backend-option').addClass('fw-backend-option-type-builder');

			fwEvents.trigger('fw:option-type:builder:init', {
				$elements: $options
			});

			$options.each(function(){
				var $this          = $(this),
					hasDragAndDrop = $this.attr('data-drag-and-drop'),
					id             = $this.attr('id'),
					type           = $this.attr('data-builder-option-type');

				/**
				 * Create instance of Builder
				 */
				{
					var data = {
						type:         type,
						$option:      $this,
						$input:       $this.find('> input:first'),
						$types:       $this.find('.builder-items-types:first'),
						$rootItems:   $this.find('.builder-root-items:first'),
						$headerTools: $('<div class="fw-builder-header-tools fw-clearfix fw-hidden"></div>')
					};

					var eventData = $.extend({}, data, {
						/**
						 * In event you can extend (customize/change) and replace this (property) class
						 */
						Builder: Builder
					});

					fwEvents.trigger('fw-builder:'+ type +':before-create', eventData);

					$this.find('> .builder-items-types').append(data.$headerTools);

					var builder = new eventData.Builder(
						{
							type: data.type
						},
						{
							$input: data.$input
						}
					);

					builder.rootItems.view.$el.appendTo(data.$rootItems);

					new fwExtBuilderRootItemsTips(builder.rootItems);
				}

				/**
				 * Init draggable thumbnails just if user wants it to be around
				 */
				if (hasDragAndDrop) {
					initDraggable($this, builder, id);
				}

				/**
				 * Add tips to thumbnails
				 */
				$this.find('.builder-items-types .builder-item-type [data-hover-tip]').each(function(){
					$(this).qtip({
						position: {
							at: 'top center',
							my: 'bottom center',
							viewport: $('body')
						},
						style: {
							classes: 'qtip-fw qtip-fw-builder',
							tip: {
								width: 12,
								height: 5
							}
						},
						content: {
							text: $(this).attr('data-hover-tip')
						}
					});
				});

				/**
				 * Make header follow you when you scroll down
				 */
				if ($this.attr('data-fixed-header')) {
					var fixedHeaderEventsNamespace = '.fw-builder-fixed-header-'+ (++fixedHeaderHelpers.increment),
						$fixedHeader = $this.find('> .builder-items-types:first'),
						/**
						 * In OptionsModal we must track the modal scroll not the window scroll
						 */
						$scrollParent;

					$scrollParent = $this.scrollParent();
					if ($scrollParent.get(0) === document || $scrollParent.get(0) === document.body) {
						$scrollParent = $(window);
					}

					/**
					 * Options modal fixed tabs are initialized after options init
					 */
					setTimeout(function(){
						$scrollParent = $this.scrollParent();
						if ($scrollParent.get(0) === document || $scrollParent.get(0) === document.body) {
							$scrollParent = $(window);
						}

						$scrollParent
							.on('scroll'+ fixedHeaderEventsNamespace, function(){
								fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);
							})
							.on('resize'+ fixedHeaderEventsNamespace, function(){
								fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);
							});

						fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);
					}, 0);

					/**
					 * On thumbnails tab change, the new tab may contain more thumbnails that previous
					 * thus having different height
					 */
					$fixedHeader.on('click'+ fixedHeaderEventsNamespace, '.fw-options-tabs-list a, .fullscreen-btn', function(){
						fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);

						/**
						 * When you scroll down to the last items (to the limit when the fixed header stops and begins to go under page)
						 * and you switch to a tab with a bigger height, there are some issues with positioning.
						 * Calling this send time fixes it
						 */
						fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);
					});

					/**
					 * Listen builder value/items change
					 * For e.g. when you delete an element from the builder (or press undo/redo buttons)
					 * its height is changed and the fixed header needs repositioning
					 */
					builder.$input.on('fw-builder:input:change'+ fixedHeaderEventsNamespace, function(){
						fixedHeaderHelpers.fix($fixedHeader, $this, $scrollParent);
					});

					/**
					 * Remove events from external elements
					 * In case the builder is created and remove dynamically multiple times, for e.g. inside fw.OptionsModal
					 */
					$this.on('remove', function(){
						$scrollParent.off(fixedHeaderEventsNamespace);
					});
				}

				$this.on('fw:option-type:builder:dump-json', function(e, data){
					if (typeof data != 'undefined' && typeof data.cid != 'undefined') {
						console.log('[Builder JSON Dump] Item '+ data.cid +'\n\n'+
							JSON.stringify(builder.findItemRecursive({cid: data.cid}))
						);
					} else {
						console.log('[Builder JSON Dump] Full\n\n'+
							JSON.stringify(builder.rootItems)
						);
					}
				});

				$this.trigger('fw:option-type:builder:init', $.extend({}, eventData, {
					builder: builder
				}));
			});

			// add special class for builders that has header tools div
			$options.find('> .builder-items-types .fw-builder-header-tools:not(.fw-hidden)')
				.closest('.fw-option-type-builder').addClass('has-header-tools')
				// Add "Save" post button if the builder is within edit post form
				.each(function(){
					var $postForm = $(this).closest('form#post');

					if (!$postForm.length) {
						return;
					}

					var isLocalStorageAvailable = function(){
							var test = 'test';
							try {
								localStorage.setItem(test, test);
								localStorage.removeItem(test);
								return true;
							} catch(e) {
								return false;
							}
						},
						localStorageKey = 'fw-ext-builder-save-scroll-position',
						$savePostButton = $postForm.find('input#publish'),
						$savePostBuilderButton = $(
							'<button'+
								' type="button"'+
								' class="button button-primary fw-pull-right fw-builder-header-post-save-button"'+
								' onclick="return false;">' +
							'</button>')
							.text($savePostButton.attr('value'))
							.on('click', function(){
								$(this).attr('disabled', 'disabled').off('click');

								if (isLocalStorageAvailable()) {
									localStorage.setItem(localStorageKey, $(window).scrollTop());
								}

								$savePostButton.trigger('click');
							});

					$(this).find('.fw-builder-header-tools:first')
						.prepend('<span class="pull-right">&nbsp;&nbsp;&nbsp;&nbsp;</span>')
						.prepend($savePostBuilderButton);

					if (isLocalStorageAvailable()) {
						var scrollTopOnLastSave = localStorage.getItem(localStorageKey);

						if (scrollTopOnLastSave !== null) {
							localStorage.removeItem(localStorageKey);

							// http://stackoverflow.com/a/16475234/1794248
							$('html, body').animate({scrollTop: scrollTopOnLastSave}, '100', 'swing');
						}
					}
				});

			$options.addClass('initialized');
		});
	}
})(jQuery);

