define(function(require) {
	var $ = require('jquery'),
		_ = require('lodash'),
		monster = require('monster');
	var appSubmodules = [
		'blacklist',
		'callcenter',
		'conference',
		'device',
		'directory',
        'eavesdrop',
		'faxbox',
		'featurecodes',
		'groups',
		'media',
		'menu',
		'misc',
		'qubicle',
		'resource',
		'temporalset',
		'timeofday',
		'user',
		'vmbox',
	];

	var appSubmodulesFullPath = _.map(appSubmodules, function(name) {
		return './submodules/' + name + '/' + name;
	})

	require(_.merge(appSubmodulesFullPath, [
		'bootstraptour'
	]));

	var app = {
		name: 'callflows',

		css: [ 'app', 'icons' ],

		i18n: {
			'en-US': { customCss: false }
		},

		// Defines API requests not included in the SDK
		requests: {},

		// Define the events available for other apps
		subscribe: {},

		subModules: appSubmodules,

		appFlags: {
			flow: {},

			// For now we use that to only load the numbers classifiers the first time we load the app, since it is very unlikely to change often
			appData: {},

			showAllCallflows: (monster.config.hasOwnProperty('developerFlags') && monster.config.developerFlags.showAllCallflows) || monster.apps.auth.originalAccount.superduper_admin
		},

		actions: {},
		categories: {},
		flow: {},

		// Method used by the Monster-UI Framework, shouldn't be touched unless you're doing some advanced kind of stuff!
		load: function(callback) {
			var self = this;

			self.initApp(function() {
				callback && callback(self);
			});
		},

		// Method used by the Monster-UI Framework, shouldn't be touched unless you're doing some advanced kind of stuff!
		initApp: function(callback) {
			var self = this;

			/* Used to init the auth token and account id of self app */
			monster.pub('auth.initApp', {
				app: self,
				callback: callback
			});

			self.initHandlebarsHelpers();
		},

		initHandlebarsHelpers: function() {
			Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) {
				var operators, result;

				if (arguments.length < 3) {
					throw new Error('Handlerbars Helper \'compare\' needs 2 parameters');
				}

				if (options === undefined) {
					options = rvalue;
					rvalue = operator;
					operator = '===';
				}

				operators = {
					'==': function (l, r) { return l == r; },
					'===': function (l, r) { return l === r; },
					'!=': function (l, r) { return l != r; },
					'!==': function (l, r) { return l !== r; },
					'<': function (l, r) { return l < r; },
					'>': function (l, r) { return l > r; },
					'<=': function (l, r) { return l <= r; },
					'>=': function (l, r) { return l >= r; },
					'typeof': function (l, r) { return typeof l == r; }
				};

				if (!operators[operator]) {
					throw new Error('Handlerbars Helper \'compare\' doesn\'t know the operator ' + operator);
				}

				result = operators[operator](lvalue, rvalue);

				if (result) {
					return options.fn(this);
				} else {
					return options.inverse(this);
				}
			});
		},

		// Entry Point of the app
		render: function(container) {
			var self = this,
				parent = _.isEmpty(container) ? $('#monster_content') : container;

			monster.pub('callflows.fetchActions', { actions: self.actions });

			self.renderEntityManager(parent);
		},

		renderCallflows: function(container) {
			var self = this,
				callflowsTemplate = $(self.getTemplate({
					name: 'callflow-manager',
					data: {
						canToggleCallflows: (monster.config.hasOwnProperty('developerFlags') && monster.config.developerFlags.showAllCallflows) || monster.apps.auth.originalAccount.superduper_admin,
						hasAllCallflows: self.appFlags.showAllCallflows
					}
				}));

			self.bindCallflowsEvents(callflowsTemplate, container);

			monster.ui.tooltips(callflowsTemplate);

			self.repaintList({
				template: callflowsTemplate,
				callback: function() {
					(container)
						.empty()
						.append(callflowsTemplate);

					container.find('.search-query').focus();

					self.hackResize(callflowsTemplate);

					if ($('.js-callflows-list .list-element').length === 0) {
						self.initGuideTour();
					}
				}
			});
		},

		initGuideTour: function() {
			var self = this;
			var i18n = self.i18n.active();

			self.tour = new Tour ({
				name: 'callflow-guide',
				backdrop: true,
				debug: true,
				template: '' +
					'<div class="popover tour">' +
						'<div class="arrow"></div>' +
						'<h3 class="popover-title"></h3>' +
						'<div class="popover-content"></div>' +
						'<div class="popover-navigation">' +
							'<button class="btn btn-default" data-role="next">' +
								i18n.callflows.guide.nextBtnText +
							'</button>' +
							'<button class="btn btn-default" data-role="end">' +
								i18n.callflows.guide.endTourBtnText +
							'</button>' +
						'</div>' +
					'</div>',
				steps: [
					{
						element: '.js-callflow-edition .js-list-add',
						placement: 'right',
						content: i18n.callflows.guide.addCallflow,
						onShown: function() {
							$('.popover').find('[data-role="prev"]').remove();
						},
						onNext: function() {
							$('.js-callflow-edition .js-list-add').click();
						}
					},
					{
						element: '#ws_callflow .js-add-number',
						placement: 'bottom',
						content: i18n.callflows.guide.addNumber,
						onShown: function() {
							$('.popover').find('[data-role="prev"]').remove();
						},
						onNext: function(tour) {
							$('#ws_callflow .js-add-number').click();
							tour.end();
						}
					},
					{
						element: '#ws_cf_tools > div',
						placement: 'left',
						content: i18n.callflows.guide.dragAndDropAction,
						onShown: function() {
							$('.popover').find('[data-role="next"],[data-role="prev"]').remove();
						}
					}
				] });

			self.tour.init();
			if (self.tour.ended()) {
				self.tour.restart();
			}

			if (self.tour.getCurrentStep() !== 0) {
				self.tour.goTo(0);
			}
		},

		bindCallflowsEvents: function(template, container) {
			var self = this,
				callflowList = template.find('.list-container .list'),
				isLoading = false,
				loader = $('<li class="content-centered list-loader"> <i class="fa fa-spinner fa-spin"></i></li>'),
				searchLink = $(self.getTemplate({
					name: 'callflowList-searchLink'
				}));

			template.find('.superadmin-mode #switch_role').on('change', function(e) {
				self.appFlags.showAllCallflows = $(this).is(':checked');

				self.renderCallflows(container);
			});

			// Add Callflow
			template.find('.list-add').on('click', function() {
				template.find('.callflow-content')
					.removeClass('listing-mode')
					.addClass('edition-mode');

				$('.list-element-active').removeClass('list-element-active');
				$(this).addClass('list-element-active');

				self.editCallflow();

				if ($('.js-callflows-list .list-element').length === 0) {
					if (self.tour.ended()) {
						self.tour.restart();
					}
					if (self.tour.getCurrentStep() !== 1) {
						self.tour.goTo(1);
					}
				}
			});

			// Edit Callflow
			callflowList.on('click', '.list-element', function() {
				var $this = $(this),
					callflowId = $this.data('id');

				$this.closest('.list').find('.list-element-active').removeClass('list-element-active');
				$this.addClass('list-element-active');

				template.find('.callflow-content')
					.removeClass('listing-mode')
					.addClass('edition-mode');

				self.editCallflow({ id: callflowId });
			});

			callflowList.on('scroll', function() {
				if (!isLoading && callflowList.data('next-key') && (callflowList.scrollTop() >= (callflowList[0].scrollHeight - callflowList.innerHeight() - 100))) {
					isLoading = true;
					callflowList.append(loader);

					self.listData({
						callback: function(callflowData) {
							var listCallflows = $(self.getTemplate({
								name: 'callflowList',
								data: {
									callflows: callflowData.data,
									activeEntityId: callflowData.id
								}
							}));

							loader.remove();

							callflowList
								.append(listCallflows)
								.data('next-key', callflowData.next_start_key || null);

							isLoading = false;
						},
						nextStartKey: callflowList.data('next-key'),
						searchValue: callflowList.data('search-value')
					});
				}
			});

			// Search list
			template.find('.search-query').on('keyup', function() {
				var search = $(this).val();

				searchLink.find('.search-value').text(search);
				if (search) {
					$.each(template.find('.list-element'), function() {
						var $elem = $(this);
						if ($elem.data('search').toLowerCase().indexOf(search.toLowerCase()) >= 0) {
							$elem.show();
						} else {
							$elem.hide();
						}
					});
					callflowList.prepend(searchLink);
				} else {
					template.find('.list-element').show();
					searchLink.remove();
				}
			});

			callflowList.on('click', '.search-link', function() {
				if (searchLink.hasClass('active')) {
					callflowList.data('search-value', null);
					searchLink
						.removeClass('active')
						.remove();
					template
						.find('.search-query')
						.prop('disabled', false)
						.val('');
					self.repaintList({template: template});
				} else {
					var searchValue = searchLink.find('.search-value').text();
					searchLink
						.addClass('active')
						.remove();
					callflowList.data('search-value', searchValue);

					template.find('.search-query').prop('disabled', true);

					self.repaintList({
						template: template,
						searchValue: searchValue,
						callback: function() {
							callflowList.prepend(searchLink);
						}
					});
				}
			});
		},

		renderEntityManager: function(container) {
			var self = this,
				entityActions = _
					.chain(self.actions)
					.filter('listEntities')
					.keyBy('module')
					.value(),
				template = $(self.getTemplate({
					name: 'layout',
					data: {
						actions: _
							.chain(entityActions)
							.map()
							.sortBy('name')
							.value()
					}
				}));

			self.bindEntityManagerEvents({
				parent: container,
				template: template,
				actions: entityActions
			});

			container
				.empty()
				.append(template);
		},

		bindEntityManagerEvents: function(args) {
			var self = this,
				template = args.template,
				actions = args.actions,
				editEntity = function(type, id) {
					monster.pub(actions[type].editEntity, {
						data: id ? { id: id } : {},
						parent: template,
						target: template.find('.entity-edition .entity-content'),
						callbacks: {
							after_render: function() {
								$(window).trigger('resize');
								template.find('.entity-edition .callflow-content').animate({ scrollTop: 0 });
							},
							save_success: function(data) {
								self.refreshEntityList({
									template: template,
									actions: actions,
									entityType: type,
									activeEntityId: data.id
								});
								editEntity(type, data.id);
							},
							delete_success: function(data) {
								self.refreshEntityList({
									template: template,
									actions: actions,
									entityType: type
								});
								template.find('.entity-edition .entity-content').empty();
							}
						}
					});
				};

			self.hackResize(template.find('.entity-edition'));

			template.find('.entity-manager .entity-element').on('click', function() {
				var $this = $(this);
				if ($this.hasClass('callflow-element')) {
					self.renderCallflows(template.find('.callflow-edition'));

					template.find('.callflow-app-section').hide();
					template.find('.callflow-edition').show();
				} else if ($this.hasClass('account-element')) {
					self.renderAccountSettings(template.find('.callflow-edition'));

					template.find('.callflow-app-section').hide();
					template.find('.callflow-edition').show();
				} else if ($this.hasClass('feature-code-element')) {
					monster.pub('callflows.featurecode.render', template.find('.callflow-edition'));

					template.find('.callflow-app-section').hide();
					template.find('.callflow-edition').show();
				} else {
					var entityType = $this.data('type');
					template.find('.entity-edition .entity-header .entity-title').text(actions[entityType].name);
					self.refreshEntityList({
						template: template,
						actions: actions,
						entityType: entityType
					});
				}
			});

			template.on('click', '.entity-header .back-button', function() {
				template.find('.entity-edition .entity-content').empty();
				template.find('.entity-edition .list-container .list').empty();
				template.find('.entity-edition .search-query').val('');
				template.find('.callflow-edition').empty();

				template.find('.callflow-app-section').hide();
				template.find('.entity-manager').show();
			});

			template.find('.entity-edition .list-add').on('click', function() {
				var type = template.find('.entity-edition .list-container .list').data('type');
				$('.entity-edition .list-container .list-element-active').removeClass('list-element-active');
				editEntity(type);
			});

			template.find('.entity-edition .list-container .list').on('click', '.list-element', function() {
				var $this = $(this),
					id = $this.data('id'),
					type = $this.parents('.list').data('type');

				$this.closest('.list').find('.list-element-active').removeClass('list-element-active');
				$this.addClass('list-element-active');

				editEntity(type, id);
			});

			template.find('.entity-edition .search-query').on('keyup', function() {
				var search = $(this).val();
				if (search) {
					$.each(template.find('.entity-edition .list-element'), function() {
						var $elem = $(this);
						if ($elem.data('search').toLowerCase().indexOf(search.toLowerCase()) >= 0) {
							$elem.show();
						} else {
							$elem.hide();
						}
					});
				} else {
					template.find('.entity-edition .list-element').show();
				}
			});
		},

		refreshEntityList: function(args) {
			var self = this,
				template = args.template,
				actions = args.actions,
				entityType = args.entityType,
				callback = args.callbacks,
				activeEntityId = args.activeEntityId || null;

			actions[entityType].listEntities(function(entities) {
				self.formatEntityData(entities, entityType);
				var listEntities = $(self.getTemplate({
					name: 'entity-list',
					data: {
						entities: entities,
						activeEntityId: activeEntityId
					}
				}));

				monster.ui.tooltips(listEntities);

				template.find('.entity-edition .list-container .list')
					.empty()
					.append(listEntities)
					.data('type', entityType);

				template.find('.callflow-app-section').hide();
				template.find('.entity-edition').show();
				template.find('.search-query').focus();

				$(window).trigger('resize');

				callback && callback();
			});
		},

		formatEntityData: function(entities, entityType) {
			var self = this;
			_.each(entities, function(entity) {
				if (entity.first_name && entity.last_name) {
					entity.displayName = entity.first_name + ' ' + entity.last_name;
				} else if (entity.name) {
					entity.displayName = entity.name;
				} else {
					entity.displayName = entity.id;
				}

				if (entityType === 'play' && entity.media_source) {
					entity.additionalInfo = self.i18n.active().callflows.media.mediaSources[entity.media_source];
				}
			});
		},

		renderAccountSettings: function(container) {
			var self = this;

			self.loadAccountSettingsData(function(accountSettingsData) {
				var formattedData = self.formatAccountSettingsData(accountSettingsData),
					template = $(self.getTemplate({
						name: 'accountSettings',
						data: _.merge({
							showPAssertedIdentity: monster.config.whitelabel.showPAssertedIdentity
						}, formattedData)
					})),
					widgetBlacklist = self.renderBlacklists(template, accountSettingsData);

				monster.ui.tooltips(template);

				// Setup input fields
				monster.ui.chosen(template.find('.cid-number-select, .preflow-callflows-dropdown'));
				monster.ui.mask(template.find('.phone-number'), 'phoneNumber');

				// Set validation rules
				monster.ui.validate(template.find('#account_settings_form'), {
					rules: {
						'caller_id.asserted.number': {
							phoneNumber: true
						},
						'caller_id.asserted.realm': {
							realm: true
						}
					},
					messages: {
						'caller_id.asserted.number': self.i18n.active().callflows.accountSettings.callerId.messages.invalidNumber
					}
				});

				container.empty().append(template);

				self.bindAccountSettingsEvents(template, accountSettingsData, widgetBlacklist);
			});
		},

		formatAccountSettingsData: function(data) {
			var formattedData = data;

			formattedData.showMediaUploadDisclosure = monster.config.whitelabel.showMediaUploadDisclosure;

			formattedData.silenceMedia = 'silence_stream://300000';

			formattedData.extra = formattedData.extra || {};
			formattedData.extra.isShoutcast = false;

			formattedData.extra.preflowCallflows = [];
			_.each(formattedData.callflows, function(callflow) {
				if (callflow.featurecode === false && callflow.numbers && callflow.numbers.length && callflow.numbers.indexOf('no_match') < 0) {
					formattedData.extra.preflowCallflows.push({
						id: callflow.id,
						friendlyName: callflow.name || callflow.numbers.toString()
					});
				}
			});

			formattedData.extra.preflowCallflows = _.sortBy(formattedData.extra.preflowCallflows, 'friendlyName');

			delete formattedData.callflows;

			if (formattedData.account.hasOwnProperty('music_on_hold') && formattedData.account.music_on_hold.hasOwnProperty('media_id')) {
				if (formattedData.account.music_on_hold.media_id.indexOf('://') >= 0) {
					if (formattedData.account.music_on_hold.media_id !== formattedData.silenceMedia) {
						formattedData.extra.isShoutcast = true;
					}
				}
			}

			if (formattedData.hasOwnProperty('outbound_flags')) {
				if (formattedData.outbound_flags.hasOwnProperty('dynamic')) {
					formattedData.outbound_flags.dynamic = formattedData.outbound_flags.dynamic.join(',');
				}
				if (formattedData.outbound_flags.hasOwnProperty('static')) {
					formattedData.outbound_flags.static = formattedData.outbound_flags.static.join(',');
				}
			}

			return formattedData;
		},

		renderBlacklists: function(template, accountSettingsData) {
			var self = this,
				items = [],
				selectedBlacklists = [];

			_.each(accountSettingsData.blacklists, function(blacklist) {
				items.push({
					key: blacklist.id,
					value: blacklist.name
				});
			});
			selectedBlacklists = _.filter(items, function(bl) {
				return (accountSettingsData.account.blacklists || []).indexOf(bl.key) >= 0;
			});

			// we return it so we can use the getSelectedItems() method later
			return monster.ui.linkedColumns(template.find('.blacklists-wrapper'), items, selectedBlacklists);
		},

		bindAccountSettingsEvents: function(template, data, widgetBlacklist) {
			var self = this,
				// account = args.account,
				mediaToUpload,
				closeUploadDiv = function(newMedia) {
					mediaToUpload = undefined;
					template.find('.upload-div input').val('');
					template.find('.upload-div').slideUp(function() {
						template.find('.upload-toggle').removeClass('active');
					});
					if (newMedia) {
						var mediaSelect = template.find('.media-dropdown');
						mediaSelect.append('<option value="' + newMedia.id + '">' + newMedia.name + '</option>');
						mediaSelect.val(newMedia.id);
					}
				};

			template.find('.account-settings-tabs a').click(function(e) {
				e.preventDefault();

				$(this).tab('show');
			});

			template.find('.media-dropdown').on('change', function() {
				template.find('.shoutcast-div').toggleClass('active', $(this).val() === 'shoutcast').find('input').val('');
			});

			template.find('.upload-input').fileUpload({
				inputOnly: true,
				wrapperClass: 'file-upload input-append',
				btnText: self.i18n.active().callflows.accountSettings.musicOnHold.audioUploadButton,
				btnClass: 'monster-button',
				maxSize: 5,
				success: function(results) {
					mediaToUpload = results[0];
				},
				error: function(errors) {
					if (errors.hasOwnProperty('size') && errors.size.length > 0) {
						monster.ui.alert(self.i18n.active().callflows.accountSettings.musicOnHold.fileTooBigAlert);
					}
					template.find('.upload-div input').val('');
					mediaToUpload = undefined;
				}
			});

			template.find('.upload-toggle').on('click', function() {
				if ($(this).hasClass('active')) {
					template.find('.upload-div').stop(true, true).slideUp();
				} else {
					template.find('.upload-div').stop(true, true).slideDown();
				}
			});

			template.find('.upload-cancel').on('click', function() {
				closeUploadDiv();
			});

			template.find('.upload-submit').on('click', function() {
				template.find('.shoutcast-div').removeClass('active');
				if (mediaToUpload) {
					self.callApi({
						resource: 'media.create',
						data: {
							accountId: self.accountId,
							data: {
								streamable: true,
								name: mediaToUpload.name,
								media_source: 'upload',
								description: mediaToUpload.name
							}
						},
						success: function(data, status) {
							var media = data.data;
							self.callApi({
								resource: 'media.upload',
								data: {
									accountId: self.accountId,
									mediaId: media.id,
									data: mediaToUpload.file
								},
								success: function(data, status) {
									closeUploadDiv(media);
								},
								error: function(data, status) {
									self.callApi({
										resource: 'media.delete',
										data: {
											accountId: self.accountId,
											mediaId: media.id,
											data: {}
										},
										success: function(data, status) {}
									});
								}
							});
						}
					});
				} else {
					monster.ui.alert(self.i18n.active().callflows.accountSettings.musicOnHold.emptyUploadAlert);
				}
			});

			template.find('.account-settings-update').on('click', function() {
				// Validate form
				if (!monster.ui.valid(template.find('#account_settings_form'))) {
					return;
				}

				// Collect data
				var formData = monster.ui.getFormData('account_settings_form'),
					newData = _.merge({}, data.account, formData);

				// Format data
				if (_.has(newData.caller_id, 'asserted')) {
					newData.caller_id.asserted.number = monster.util.getFormatPhoneNumber(newData.caller_id.asserted.number).e164Number;
				}

				// Clean empty data
				if (formData.music_on_hold.media_id === '') {
					delete newData.music_on_hold.media_id;
				} else if (formData.music_on_hold.media_id === 'shoutcast') {
					newData.music_on_hold.media_id = template.find('.shoutcast-url-input').val();
				}

				self.compactObject(newData.caller_id);

				if (_.isEmpty(newData.caller_id)) {
					delete newData.caller_id;
				}

				if (formData.preflow.always === '_disabled') {
					delete newData.preflow.always;
				}

				if (newData.hasOwnProperty('outbound_flags')) {
					newData.outbound_flags.dynamic = newData.outbound_flags.dynamic ? newData.outbound_flags.dynamic.split(',') : [];
					newData.outbound_flags.static = newData.outbound_flags.static ? newData.outbound_flags.static.split(',') : [];
					_.isEmpty(newData.outbound_flags.dynamic) && delete newData.outbound_flags.dynamic;
					_.isEmpty(newData.outbound_flags.static) && delete newData.outbound_flags.static;
					_.isEmpty(newData.outbound_flags) && delete newData.outbound_flags;
				}

				newData.blacklists = widgetBlacklist.getSelectedItems();

				delete newData.extra;

				self.callApi({
					resource: 'account.update',
					data: {
						accountId: newData.id,
						data: newData
					},
					success: function(data, status) {
						self.render();
					}
				});
			});
		},

		loadAccountSettingsData: function(callback) {
			var self = this;
			monster.parallel({
				account: function(parallelCallback) {
					self.callApi({
						resource: 'account.get',
						data: {
							accountId: self.accountId
						},
						success: function(data, status) {
							parallelCallback && parallelCallback(null, data.data);
						}
					});
				},
				callflows: function(parallelCallback) {
					self.callApi({
						resource: 'callflow.list',
						data: {
							accountId: self.accountId,
							filters: {
								paginate: false
							}
						},
						success: function(data, status) {
							parallelCallback && parallelCallback(null, data.data);
						}
					});
				},
				mediaList: function(parallelCallback) {
					self.callApi({
						resource: 'media.list',
						data: {
							accountId: self.accountId,
							filters: {
								paginate: false
							}
						},
						success: function(data, status) {
							parallelCallback && parallelCallback(null, data.data);
						}
					});
				},
				numberList: function(parallelCallback) {
					self.callApi({
						resource: 'numbers.list',
						data: {
							accountId: self.accountId,
							filters: {
								paginate: false
							}
						},
						success: function(data, status) {
							parallelCallback && parallelCallback(null, data.data.numbers);
						}
					});
				},
				blacklists: function(parallelCallback) {
					self.callApi({
						resource: 'blacklist.list',
						data: {
							accountId: self.accountId,
							filters: {
								paginate: false
							}
						},
						success: function(data, status) {
							parallelCallback && parallelCallback(null, data.data);
						}
					});
				}
			}, function(err, results) {
				callback && callback(results);
			});
		},

		hackResize: function(container) {
			var self = this;

			// Adjusting the layout divs height to always fit the window's size
			$(window).resize(function(e) {
				var $listContainer = container.find('.list-container'),
					$mainContent = container.find('.callflow-content'),
					$tools = container.find('.tools'),
					$flowChart = container.find('.flowchart'),
					contentHeight = window.innerHeight - $('.core-topbar-wrapper').outerHeight(),
					contentHeightPx = contentHeight + 'px',
					innerContentHeightPx = (contentHeight - 71) + 'px';

				$listContainer.css('height', window.innerHeight - $listContainer.position().top + 'px');
				$mainContent.css('height', contentHeightPx);
				$tools.css('height', innerContentHeightPx);
				$flowChart.css('height', innerContentHeightPx);
			});
			$(window).resize();
		},

		repaintList: function(args) {
			var self = this,
				args = args || {},
				template = args.template || $('#callflow_container'),
				callback = args.callback;

			self.listData({
				callback: function(callflowData) {
					var listCallflows = $(self.getTemplate({
						name: 'callflowList',
						data: {
							callflows: callflowData.data,
							activeEntityId: args.id
						}
					}));

					template.find('.list-container .list')
						.empty()
						.append(listCallflows)
						.data('next-key', callflowData.next_start_key || null);

					callback && callback(callflowData.data);
				},
				searchValue: args.searchValue
			});
		},

		listData: function(args) {
			var self = this,
				nextStartKey = args.nextStartKey,
				searchValue = args.searchValue,
				callback = args.callback,
				apiResource = 'callflow.list',
				apiData = {
					accountId: self.accountId
				};

			if (nextStartKey) {
				$.extend(true, apiData, {
					filters: {
						'start_key': encodeURIComponent(nextStartKey)
					}
				});
			}

			if (!self.appFlags.showAllCallflows) {
				$.extend(true, apiData, {
					filters: {
						'filter_not_ui_metadata.origin': [
							'voip',
							'callqueues'
						]
					}
				});
			}

			if (searchValue) {
				apiResource = 'callflow.searchByNameAndNumber';
				apiData.value = encodeURIComponent(searchValue);
			}

			self.callApi({
				resource: apiResource,
				data: apiData,
				success: function(callflowData) {
					var returnedCallflowData = self.formatData(callflowData);

					callback && callback(returnedCallflowData);
				}
			});
		},

		listNumbers: function(callback) {
			var self = this;

			self.callApi({
				resource: 'numbers.list',
				data: {
					accountId: self.accountId,
					filters: {
						paginate: false
					}
				},
				success: function(numbers) {
					numbers = self.formatListSpareNumbers(numbers.data.numbers);

					callback && callback(numbers);
				}
			});
		},

		formatListSpareNumbers: function(numbers) {
			var self = this,
				listSpareNumbers = [];

			_.each(numbers, function(numberData, phoneNumber) {
				if (!numberData.hasOwnProperty('used_by') || numberData.used_by === '') {
					numberData.phoneNumber = phoneNumber;
					listSpareNumbers.push(numberData);
				}
			});

			return listSpareNumbers;
		},

		formatData: function(data) {
			var formattedList = [];

			_.each(data.data, function(callflow) {
				var formattedNumbers = _.map(callflow.numbers || '-', function(number) {
						return _.startsWith('+', number)
							? monster.util.formatPhoneNumber(number)
							: number;
					}),
					listNumbers = formattedNumbers.toString(),
					isFeatureCode = callflow.featurecode !== false && !_.isEmpty(callflow.featurecode);

				if (!isFeatureCode) {
					if (callflow.name) {
						callflow.description = listNumbers;
						callflow.title = callflow.name;
					} else {
						callflow.title = listNumbers;
					}

					formattedList.push(callflow);
				}
			});

			formattedList.sort(function(a, b) {
				return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : 1;
			});

			data.data = formattedList;

			return data;
		},

		editCallflow: function(data) {
			var self = this;

			delete self.original_flow; // clear original_flow

			$('.js-callflow-intro').hide();
			$('#callflow-view .callflow_help').hide();

			self.resetFlow();

			if (data && data.id) {
				self.callApi({
					resource: 'callflow.get',
					data: {
						accountId: self.accountId,
						callflowId: data.id
					},
					success: function(callflow) {
						var callflow = callflow.data;

						//self.resetFlow();
						self.dataCallflow = callflow;

						self.flow.id = callflow.id;
						self.flow.name = callflow.name;
						self.flow.contact_list = { exclude: 'contact_list' in callflow ? callflow.contact_list.exclude || false : false };
						self.flow.caption_map = callflow.metadata;

						if (callflow.flow.module !== undefined) {
							self.flow.root = self.buildFlow(callflow.flow, self.flow.root, 0, '_');
						}

						self.flow.numbers = callflow.numbers || [];

						self.repaintFlow();
					}
				});
			} else {
				self.resetFlow();
				self.dataCallflow = {};
				self.repaintFlow();
			}

			self.renderButtons();
			self.renderTools();
		},

		renderButtons: function() {
			var self = this,
				buttons = $(self.getTemplate({
					name: 'buttons'
				}));

			$('.buttons').empty();

			$('.save', buttons).click(function() {
				if (self.flow.numbers && self.flow.numbers.length > 0) {
					self.save();
				} else {
					monster.ui.alert(self.i18n.active().oldCallflows.invalid_number + '<br/><br/>' + self.i18n.active().oldCallflows.please_select_valid_number);
				}
			});

			$('.delete', buttons).click(function() {
				if (self.flow.id) {
					monster.ui.confirm(self.i18n.active().oldCallflows.are_you_sure, function() {
						self.callApi({
							resource: 'callflow.delete',
							data: {
								accountId: self.accountId,
								callflowId: self.flow.id
							},
							success: function() {
								$('#ws_cf_flow').empty();
								$('.buttons').empty();
								$('#ws_cf_tools').empty();
								$('#hidden_callflow_warning').hide();

								self.repaintList();
								self.resetFlow();
							}
						});

						self.show_pending_change(false);
					});
				} else {
					monster.ui.alert(self.i18n.active().oldCallflows.this_callflow_has_not_been_created);
				}
			});

			$('.buttons').append(buttons);
		},

		// Callflow JS code
		buildFlow: function(json, parent, id, key) {
			var self = this,
				branch = self.branch(self.construct_action(json));

			branch.data.data = _.get(json, 'data', {});
			branch.id = ++id;
			branch.key = key;
			branch.disabled = _.get(json, 'data.skip_module');

			branch.caption = self.actions.hasOwnProperty(branch.actionName) ? self.actions[branch.actionName].caption(branch, self.flow.caption_map) : '';

			if (self.actions.hasOwnProperty(parent.actionName) && self.actions[parent.actionName].hasOwnProperty('key_caption')) {
				branch.key_caption = self.actions[parent.actionName].key_caption(branch, self.flow.caption_map);
			}

			if (json.hasOwnProperty('children')) {
				$.each(json.children, function(key, child) {
					branch = self.buildFlow(child, branch, id, key);
				});
			}

			parent.addChild(branch);

			return parent;
		},

		construct_action: function(json) {
			var action = '';

			if ('data' in json) {
				if ('id' in json.data) {
					action = 'id=*,';
				}

				if ('action' in json.data) {
					action += 'action=' + json.data.action + ',';
				}
			}

			if (action !== '') {
				action = '[' + action.replace(/,$/, ']');
			} else {
				action = '[]';
			}

			return json.module + action;
		},

		resetFlow: function() {
			var self = this;

			self.flow = {};
			self.flow.root = self.branch('root'); // head of the flow tree
			self.flow.root.key = 'flow';
			self.flow.numbers = [];
			self.flow.caption_map = {};
			self.formatFlow();
		},

		formatFlow: function() {
			var self = this;

			self.flow.root.index(0);
			self.flow.nodes = self.flow.root.nodes();
		},

		// Create a new branch node for the flow
		branch: function(actionName) {
			var self = this;

			function Branch(actionName) {
				var that = this,
					action = self.actions[actionName] || {};

				this.id = -1;
				this.actionName = actionName;
				this.module = action.module;
				this.key = '_';
				this.parent = null;
				this.children = [];
				this.data = {
					data: $.extend(true, {}, action.data)
				};
				this.caption = '';
				this.key_caption = '';

				this.potentialChildren = function() {
					var list = [];

					for (var i in self.actions) {
						if (self.actions[i].isUsable) {
							list[i] = i;
						}
					}

					for (var i in action.rules) {
						var rule = action.rules[i];

						if (rule.type === 'quantity' && this.children.length >= rule.maxSize) {
							list = [];
						}
					}

					return list;
				};

				this.contains = function(branch) {
					var toCheck = branch;

					while (toCheck.parent) {
						if (this.id === toCheck.id) {
							return true;
						} else {
							toCheck = toCheck.parent;
						}
					}

					return false;
				};

				this.removeChild = function(branch) {
					$.each(this.children, function(i, child) {
						if (child.id === branch.id) {
							that.children.splice(i, 1);
							return false;
						}
					});
				};

				this.addChild = function(branch) {
					if (!(branch.actionName in this.potentialChildren())) {
						return false;
					}

					if (branch.contains(this)) {
						return false;
					}

					if (branch.parent) {
						branch.parent.removeChild(branch);
					}

					branch.parent = this;

					this.children.push(branch);

					return true;
				};

				this.getMetadata = function(key, defaultValue) {
					var value;

					if (_.has(this.data, ['data', key])) {
						value = this.data.data[key];

						return (value === 'null') ? null : value;
					}

					return _.isUndefined(defaultValue) ? false : defaultValue;
				};

				this.setMetadata = function(key, value) {
					if (!('data' in this.data)) {
						this.data.data = {};
					}

					this.data.data[key] = (value == null) ? 'null' : value;
				};

				this.deleteMetadata = function(key) {
					if ('data' in this.data && key in this.data.data) {
						delete this.data.data[key];
					}
				};

				this.index = function(index) {
					this.id = index;

					$.each(this.children, function() {
						index = this.index(index + 1);
					});

					return index;
				};

				this.nodes = function() {
					var nodes = {};

					nodes[this.id] = this;

					$.each(this.children, function() {
						var buf = this.nodes();

						$.each(buf, function() {
							nodes[this.id] = this;
						});
					});

					return nodes;
				};

				this.serialize = function() {
					var json = $.extend(true, {}, this.data);

					json.module = this.module;

					json.children = {};

					$.each(this.children, function() {
						json.children[this.key] = this.serialize();
					});

					return json;
				};
			}

			return new Branch(actionName);
		},

		repaintFlow: function() {
			var self = this;

			// Let it there for now, if we need to save callflows automatically again.
			/*if ('savable' in THIS.flow) {
				THIS.save_callflow_no_loading();
			}*/

			self.flow.savable = true;

			var target = $('#ws_cf_flow').empty();

			target.append(this.getUIFlow());

			var current_flow = self.stringify_flow(self.flow);

			if (!('original_flow' in self) || self.original_flow.split('|')[0] !== current_flow.split('|')[0]) {
				self.original_flow = current_flow;
				self.show_pending_change(false);
			} else {
				self.show_pending_change(self.original_flow !== current_flow);
			}

			var metadata = self.dataCallflow.hasOwnProperty('ui_metadata') ? self.dataCallflow.ui_metadata : false,
				isHiddenCallflow = metadata && metadata.hasOwnProperty('origin') && _.includes(['voip', 'migration', 'mobile', 'callqueues'], metadata.origin);

			isHiddenCallflow ? $('#hidden_callflow_warning').show() : $('#hidden_callflow_warning').hide();
		},

		show_pending_change: function(pending_change) {
			var self = this;
			if (pending_change) {
				$('#pending_change', '#ws_callflow').show();
				$('.save', '#ws_callflow').addClass('pulse-box');
			} else {
				$('#pending_change', '#ws_callflow').hide();
				$('.save', '#ws_callflow').removeClass('pulse-box');
			}
		},

		// We add this function because today the stringify flow doesn't handle arrays well
		// For instance in ring groups, if we change the timeout of a member, it won't toggle the "pending changes" warning
		stringify_obj: function(obj) {
			var self = this,
				str = '[';

			_.each(obj, function(v, k) {
				// Had to add this check since when we list objects with sortable, we usually just add items with .data(), but it includes sortableItem from the jQuery plugin for a short time.
				// If we don't avoid it, then we run into a JS Exception
				if (k !== 'sortableItem') {
					if (typeof v === 'object') {
						str += k + ':' + self.stringify_obj(v);
					} else if (['boolean', 'string', 'number'].indexOf(typeof v) >= 0) {
						str += k + ':' + v;
					}

					str += '|';
				}
			});

			str += ']';

			return str;
		},

		stringify_flow: function(flow) {
			var self = this,
				s_flow = flow.id + '|' + (!flow.name ? 'undefined' : flow.name),
				first_iteration;
			s_flow += '|NUMBERS';
			$.each(flow.numbers, function(key, value) {
				s_flow += '|' + value;
			});
			s_flow += '|NODES';
			$.each(flow.nodes, function(key, value) {
				s_flow += '|' + key + '::';
				first_iteration = true;

				$.each(value.data.data, function(k, v) {
					if (!first_iteration) {
						s_flow += '//';
					} else {
						first_iteration = false;
					}

					if (typeof v !== 'object') {
						s_flow += k + ':' + v;
					} else {
						s_flow += k + ':' + self.stringify_obj(v);
					}
				});
			});

			return s_flow;
		},

		getCallflowPreview: function(data, callback) {
			var self = this,
				layout;

			self.callApi({
				resource: 'callflow.get',
				data: {
					accountId: self.accountId,
					callflowId: data.id
				},
				success: function(callflow) {
					var callflow = callflow.data,
						flow = {};

					flow.root = self.branch('root');
					flow.root.key = 'flow';
					flow.numbers = [];
					flow.caption_map = {};
					flow.root.index(0);
					flow.nodes = flow.root.nodes();

					flow.id = callflow.id;
					flow.name = callflow.name;
					flow.contact_list = { exclude: 'contact_list' in callflow ? callflow.contact_list.exclude || false : false };
					flow.caption_map = callflow.metadata;

					if (callflow.flow.module !== undefined) {
						flow.root = self.buildFlow(callflow.flow, flow.root, 0, '_');
					}

					flow.nodes = flow.root.nodes();
					flow.numbers = callflow.numbers || [];

					//prepare html from callflow

					layout = self.renderBranch(flow.root);
					callback && callback(layout);

					$('.node', layout).each(function() {
						var node = flow.nodes[$(this).attr('id')],
							$node = $(this),
							node_html;

						if (node.actionName === 'root') {
							$node.removeClass('icons_black root');
							node_html = $(self.getTemplate({
								name: 'root',
								data: {
									name: flow.name || 'Callflow'
								}
							}));

							for (var counter, size = flow.numbers.length, j = Math.floor((size) / 2) + 1, i = 0; i < j; i++) {
								counter = i * 2;

								var numbers = flow.numbers.slice(counter, (counter + 2 < size) ? counter + 2 : size),
									row = $(self.getTemplate({
										name: 'rowNumber',
										data: {
											numbers: numbers
										}
									}));

								node_html
									.find('.content')
									.append(row);
							}
						} else {
							node_html = $(self.getTemplate({
								name: 'node',
								data: {
									node: node,
									callflow: self.actions[node.actionName]
								}
							}));
						}
						$(this).append(node_html);
					});
				}
			});
		},

		getUIFlow: function() {
			var self = this;

			self.formatFlow();

			var layout = self.renderBranch(self.flow.root);

			$('.node', layout).hover(function() {
				$(this).addClass('over');
			}, function() {
				$(this).removeClass('over');
			});

			$('.node', layout).each(function() {
				var node = self.flow.nodes[$(this).attr('id')],
					$node = $(this),
					node_html;

				if (node.actionName === 'root') {
					$node.removeClass('icons_black root');
					node_html = $(self.getTemplate({
						name: 'root',
						data: {
							name: self.flow.name || 'Callflow'
						}
					}));

					$('.edit_icon', node_html).click(function() {
						self.flow = $.extend(true, { contact_list: { exclude: false } }, self.flow);

						var dialogTemplate = $(self.getTemplate({
								name: 'editName',
								data: {
									name: self.flow.name,
									exclude: self.flow.contact_list.exclude,
									ui_is_main_number_cf: self.dataCallflow.hasOwnProperty('ui_is_main_number_cf') ? self.dataCallflow.ui_is_main_number_cf : false
								}
							})),
							popup = monster.ui.dialog(dialogTemplate, {
								title: self.i18n.active().oldCallflows.popup_title
							});

						$('#add', popup).click(function() {
							var $callflow_name = $('#callflow_name', popup);
							if ($callflow_name.val() !== '') {
								self.flow.name = $callflow_name.val();
								$('.root .top_bar .name', layout).html(self.flow.name);
							} else {
								self.flow.name = '';
								$('.root .top_bar .name', layout).html('Callflow');
							}
							self.flow.contact_list = {
								exclude: $('#callflow_exclude', popup).prop('checked')
							};
							self.dataCallflow.ui_is_main_number_cf = $('#ui_is_main_number_cf', popup).prop('checked');
							//self.save_callflow_no_loading();
							self.repaintFlow();
							popup.dialog('close');
						});
					});

					for (var counter, size = self.flow.numbers.length, j = Math.floor((size) / 2) + 1, i = 0; i < j; i++) {
						counter = i * 2;

						var numbers = self.flow.numbers.slice(counter, (counter + 2 < size) ? counter + 2 : size),
							row = $(self.getTemplate({
								name: 'rowNumber',
								data: {
									numbers: _.map(numbers, function(number) {
										return _.startsWith('+', number)
											? monster.util.formatPhoneNumber(number)
											: number;
									})
								}
							}));

						node_html
							.find('.content')
							.append(row);
					}

					$('.number_column.empty', node_html).click(function() {
						if (self.tour) {
							self.tour.end();
						}
						self.listNumbers(function(phoneNumbers) {
							var parsedNumbers = [];

							_.each(phoneNumbers, function(number) {
								if ($.inArray(number.phoneNumber, self.flow.numbers) < 0) {
									parsedNumbers.push(number);
								}
							});

							var popup_html = $(self.getTemplate({
									name: 'addNumber',
									data: {
										phoneNumbers: parsedNumbers
									}
								})),
								popup = monster.ui.dialog(popup_html, {
									title: self.i18n.active().oldCallflows.add_number
								});

							monster.ui.chosen(popup_html.find('#list_numbers'), {
								width: '160px'
							});
							// Have to do that so that the chosen dropdown isn't hidden.
							popup_html.parent().css('overflow', 'visible');

							if (parsedNumbers.length === 0) {
								$('#list_numbers', popup_html).attr('disabled', 'disabled');
								$('<option value="select_none">' + self.i18n.active().oldCallflows.no_phone_numbers + '</option>').appendTo($('#list_numbers', popup_html));
							}

							var refresh_numbers = function() {
								self.listNumbers(function(refreshedNumbers) {
									$('#list_numbers', popup).empty();

									if (refreshedNumbers.length === 0) {
										$('#list_numbers', popup).attr('disabled', 'disabled');
										$('<option value="select_none">' + self.i18n.active().oldCallflows.no_phone_numbers + '</option>').appendTo($('#list_numbers', popup));
									} else {
										$('#list_numbers', popup).removeAttr('disabled');
										$.each(refreshedNumbers, function(k, v) {
											$('<option value="' + v + '">' + v + '</option>').appendTo($('#list_numbers', popup));
										});
									}
								});
							};

							$('.extensions_content', popup).hide();

							$('input[name="number_type"]', popup).click(function() {
								if ($(this).val() === 'your_numbers') {
									$('.list_numbers_content', popup).show();
									$('.extensions_content', popup).hide();
								} else {
									$('.extensions_content', popup).show();
									$('.list_numbers_content', popup).hide();
								}
							});

							popup.find('.buy-link').on('click', function(e) {
								e.preventDefault();
								monster.pub('common.buyNumbers', {
									searchType: $(this).data('type'),
									callbacks: {
										success: function(numbers) {
											var lastNumber;

											_.each(numbers, function(number, k) {
												popup.find('#list_numbers').append($('<option value="' + k + '">' + monster.util.formatPhoneNumber(k) + '</option>'));
												lastNumber = k;
											});

											popup.find('#list_numbers').val(lastNumber).trigger('chosen:updated');
										}
									}
								});
							});

							popup.find('.search-extension-link').on('click', function() {
								monster.pub('common.extensionTools.select', {
									callback: function(number) {
										popup.find('#add_number_text').val(number);
									}
								});
							});

							$('.add_number', popup).click(function(event) {
								event.preventDefault();
								var number = $('input[name="number_type"]:checked', popup).val() === 'your_numbers' ? $('#list_numbers option:selected', popup).val() : $('#add_number_text', popup).val();

								if (number !== 'select_none' && number !== '') {
									self.flow.numbers.push(number);
									popup.dialog('close');

									self.repaintFlow();

									if (self.tour
										&& $('#ws_callflow .number_column').not('.js-add-number').length === 1) {
										if (self.tour.ended()) {
											self.tour.restart();
										}
										if (self.tour.getCurrentStep() !== 2) {
											self.tour.goTo(2);
										}
									}
								} else {
									monster.ui.alert(self.i18n.active().oldCallflows.you_didnt_select);
								}
							});
						});
					});

					$('.number_column .delete', node_html).click(function() {
						var number = $(this).parent('.number_column').data('number') + '',
							index = $.inArray(number, self.flow.numbers);

						if (index >= 0) {
							self.flow.numbers.splice(index, 1);
						}

						self.repaintFlow();
					});
				} else {
					node_html = $(self.getTemplate({
						name: 'node',
						data: {
							node: node,
							callflow: self.actions[node.actionName]
						}
					}));

					// If an API request takes some time, the user can try to re-click on the element, we do not want to let that re-fire a request to the back-end.
					// So we set a 500ms debounce wait that will prevent any other interaction with the callflow element.
					node_html.find('.module').on('click', _.debounce(function() {
						monster.waterfall([
							function(waterfallCallback) {
								if (node.disabled) {
									monster.ui.confirm(self.i18n.active().callflowsApp.editor.confirmDialog.enableModule.text,
										function() {
											waterfallCallback(null, false);
										},
										function() {
											waterfallCallback(null, true);
										}, {
											cancelButtonText: self.i18n.active().callflowsApp.editor.confirmDialog.enableModule.cancel,
											confirmButtonText: self.i18n.active().callflowsApp.editor.confirmDialog.enableModule.ok
										});
								} else {
									waterfallCallback(null, null);
								}
							}
						], function(err, disabled) {
							if (!_.isNull(disabled)) {
								node.disabled = disabled;
								if (_.has(node, 'data.data.skip_module')) {
									node.data.data.skip_module = disabled;
								}

								if (!disabled) {
									node_html.closest('.node').removeClass('disabled');
								}
							}

							self.actions[node.actionName].edit(node, function() {
								self.repaintFlow();
							});
						});
					}, 500, {
						leading: true,
						trailing: false
					}));
				}

				//make names of callflow nodes clickable
				$('.details a', node_html).click(function(event) {
					event.stopPropagation();
					var previewCallflowId = self.flow.nodes[$(node_html).find('.delete').attr('id')].data.data.id,
						dialogTemplate = $(self.getTemplate({
							name: 'callflows-callflowElementDetails',
							data: {
								id: previewCallflowId
							}
						})),
						popup;
					self.getCallflowPreview({id: previewCallflowId}, function(callflowPreview) {
						popup = monster.ui.dialog(dialogTemplate, {
							position: ['top', 20], // put preview near top of screen to have lots of space for it
							title: self.i18n.active().oldCallflows.callflow_preview_title,
							width: '650px'
						});
						popup.find('.callflow-preview-section.callflow').append(callflowPreview);
						$('#callflow_jump').click(function() {
							self.editCallflow({id: previewCallflowId});
							popup.dialog('close').remove();
						});
					});
				});

				$(this).append(node_html);

				$(this).droppable({
					drop: function(event, ui) {
						var target = self.flow.nodes[$(this).attr('id')],
							action,
							branch;

						if (ui.draggable.hasClass('action')) {
							action = ui.draggable.attr('name');

							branch = self.branch(action);
							branch.caption = self.actions[action].caption(branch, self.flow.caption_map);

							if (target.addChild(branch)) {
								if (branch.parent && ('key_caption' in self.actions[branch.parent.actionName])) {
									branch.key_caption = self.actions[branch.parent.actionName].key_caption(branch, self.flow.caption_map);

									self.actions[branch.parent.actionName].key_edit(branch, function() {
										self.actions[action].edit(branch, function() {
											self.repaintFlow();
										});
									});
								} else {
									self.actions[action].edit(branch, function() {
										self.repaintFlow();
									});
								}

								//This is just in case something goes wrong with the dialog
								self.repaintFlow();
							}
						}

						if (ui.draggable.hasClass('node')) {
							var branch = self.flow.nodes[ui.draggable.attr('id')];

							if (target.addChild(branch)) {
								// If we move a node, destroy its key
								branch.key = '_';

								if (branch.parent && ('key_caption' in self.actions[branch.parent.actionName])) {
									branch.key_caption = self.actions[branch.parent.actionName].key_caption(branch, self.flow.caption_map);
								}

								ui.draggable.remove();
								self.repaintFlow();
							}
						}
					}
				});

				// dragging the whole branch
				if ($(this).attr('name') !== 'root') {
					$(this).draggable({
						start: function() {
							var children = $(this).next(),
								top = children.offset().top - $(this).offset().top,
								left = children.offset().left - $(this).offset().left;

							self.enableDestinations($(this));

							$(this).attr('t', top); $(this).attr('l', left);
						},
						drag: function() {
							var children = $(this).next(),
								top = $(this).offset().top + parseInt($(this).attr('t')),
								left = $(this).offset().left + parseInt($(this).attr('l'));

							children.offset({ top: top, left: left });
						},
						stop: function() {
							self.disableDestinations();

							self.repaintFlow();
						}
					});
				}
			});

			$('.node-options .delete', layout).click(function() {
				var node = self.flow.nodes[$(this).attr('id')];

				if (node.parent) {
					node.parent.removeChild(node);

					self.repaintFlow();
				}
			});

			$('.node-options .jump', layout).click(function() {
                var $this = $(this),
                    callflowId = $this.attr('id');
                self.editCallflow({ id: callflowId });
            });

			return layout;
		},

		renderBranch: function(branch) {
			var self = this,
				flow = $(self.getTemplate({
					name: 'branch',
					data: {
						node: branch,
						display_key: branch.parent && ('key_caption' in self.actions[branch.parent.actionName])
					}
				})),
				children;

			if (branch.parent && ('key_edit' in self.actions[branch.parent.actionName])) {
				$('.div_option', flow).click(function() {
					self.actions[branch.parent.actionName].key_edit(branch, function() {
						self.repaintFlow();
					});
				});
			}

			// This need to be evaluated before the children start adding content
			children = $('.children', flow);

			$.each(branch.children, function() {
				children.append(self.renderBranch(this));
			});

			return flow;
		},

		renderTools: function() {
			var self = this,
				advanced_cat = self.i18n.active().oldCallflows.advanced_cat,
				basic_cat = self.i18n.active().oldCallflows.basic_cat,
				dataTemplate = { categories: [] },
				categories = {},
				target,
				tools;

			categories[basic_cat] = [];
			categories[advanced_cat] = [];

			$.each(self.actions, function(i, data) {
				if ('category' in data && (!data.hasOwnProperty('isListed') || data.isListed)) {
					data.category in categories ? true : categories[data.category] = [];
					data.key = i;
					categories[data.category].push(data);
				}
			});

			$.each(categories, function(key, val) {
				if (key !== basic_cat && key !== advanced_cat) {
					dataTemplate.categories.push({ key: key, actions: val });
				}
			});

			dataTemplate.categories.sort(function(a, b) {
				return a.key < b.key ? 1 : -1;
			});

			dataTemplate.categories.unshift({
				key: basic_cat,
				actions: categories[basic_cat]
			}, {
				key: advanced_cat,
				actions: categories[advanced_cat]
			});

			$.each(categories, function(idx, val) {
				val.sort(function(a, b) {
					if (a.hasOwnProperty('weight')) {
						return a.weight > b.weight ? 1 : -1;
					}
				});
			});

			tools = $(self.getTemplate({
				name: 'tools',
				data: dataTemplate
			}));

			// Set the basic drawer to open
			$('#Basic', tools).removeClass('inactive').addClass('active');

			$('.category .open', tools).click(function() {
				tools
					.find('.category')
					.removeClass('active')
					.addClass('inactive');

				$(this)
					.parent('.category')
					.removeClass('inactive')
					.addClass('active');
			});

			var help_box = $('.callflow_helpbox_wrapper', '#callflow-view').first(),
				$allActions = tools.find('.tool');

			tools.find('.search-query').on('keyup', function() {
				// debounce executes a function after a delay if it hasn't been called again
				_.debounce(function(arg) {
					var $this = arg,
						val = $this.val().toLowerCase(),
						categories = [];

					if (val) {
						tools.find('.category').removeClass('active').addClass('inactive');

						$allActions.each(function() {
							var $thisAction = $(this);

							if ($thisAction.data('name').toLowerCase().indexOf(val) >= 0) {
								$thisAction.show();
								categories.push($thisAction.parents('.category').attr('id'));
							} else {
								$thisAction.hide();
							}
						});
					} else {
						tools.find('.category').removeClass('active').addClass('inactive');
						tools.find('.category').first().removeClass('inactive').addClass('active');
						tools.find('.tool').show();
					}

					categories = _.uniq(categories);
					_.each(categories, function(category) {
						tools.find('.category[name="' + category + '"]').addClass('active').removeClass('inactive');
					});
				}, 200)($(this));
			});

			$('.tool', tools).hover(
				function() {
					var $this = $(this);
					if ($this.attr('help')) {
						tools.find('.callflow_helpbox_wrapper #help_box').html($this.attr('help'));
						tools.find('.callflow_helpbox_wrapper').css('top', $this.offset().top).css('left', $('#ws_cf_tools').offset().left - 162).show();
					}
				},
				function() {
					tools.find('.callflow_helpbox_wrapper').hide();
				}
			);

			function action(el) {
				el.draggable({
					start: function() {
						self.enableDestinations($(this));
						$(this).addClass('inactive');
					},
					drag: function() {
						if (self.tour) {
							self.tour.end();
						}
						$('.callflow_helpbox_wrapper', '#callflow-view').hide();
					},
					stop: function() {
						self.disableDestinations();
						$(this).removeClass('inactive');
					},
					containment: $('body'),
					helper: 'clone'
				});
			}

			$('.action', tools).each(function() {
				action($(this));
			});

			target = $('#ws_cf_tools').empty();
			target.append(tools);

			$('#ws_cf_tools', '#callflow-view').disableSelection();
		},

		enableDestinations: function(el) {
			var self = this;

			$('.node').each(function() {
				var activate = true,
					target = self.flow.nodes[$(this).attr('id')];

				if (el.attr('name') in target.potentialChildren()) {
					if (el.hasClass('node') && self.flow.nodes[el.attr('id')].contains(target)) {
						activate = false;
					}
				} else {
					activate = false;
				}

				if (activate) {
					$(this).addClass('active');
				} else {
					$(this).addClass('inactive');
					$(this).droppable('disable');
				}
			});
		},

		disableDestinations: function() {
			$('.node').each(function() {
				$(this).removeClass('active');
				$(this).removeClass('inactive');
				$(this).droppable('enable');
			});

			$('.tool').removeClass('active');
		},

		save: function() {
			var self = this;

			if (self.flow.numbers && self.flow.numbers.length > 0) {
				var data_request = {
					numbers: self.flow.numbers,
					flow: (self.flow.root.children[0] === undefined) ? {} : self.flow.root.children[0].serialize()
				};

				if (self.flow.name !== '') {
					data_request.name = self.flow.name;
				} else {
					delete data_request.name;
					delete self.dataCallflow.name;
				}

				if ('contact_list' in self.flow) {
					data_request.contact_list = { exclude: self.flow.contact_list.exclude || false };
				}

				// We don't want to keep the old data from the flow, so we override it with what's on the current screen before the extend.
				self.dataCallflow.flow = data_request.flow;
				// Change dictated by the new field added by monster-ui. THis way we can potentially update callflows in Kazoo UI without breaking monster.
				data_request = $.extend(true, {}, self.dataCallflow, data_request);
				delete data_request.metadata;

				if (self.flow.id) {
					self.callApi({
						resource: 'callflow.update',
						data: {
							accountId: self.accountId,
							callflowId: self.flow.id,
							data: data_request
						},
						success: function(json) {
							self.repaintList({ id: json.data.id });
							self.editCallflow({ id: json.data.id });
						}
					});
				} else {
					self.callApi({
						resource: 'callflow.create',
						data: {
							accountId: self.accountId,
							data: data_request
						},
						success: function(json) {
							self.repaintList({ id: json.data.id });
							self.editCallflow({ id: json.data.id });
						}
					});
				}
			} else {
				monster.ui.alert(self.i18n.active().oldCallflows.you_need_to_select_a_number);
			}
		},

		winkstartTabs: function(template, advanced) {
			var buttons = template.find('.view-buttons'),
				tabs = template.find('.tabs');

			if (advanced) {
				buttons.find('.btn').removeClass('activate');
				buttons.find('.advanced').addClass('activate');
			} else {
				if (monster.config.advancedView) {
					buttons.find('.btn').removeClass('activate');
					buttons.find('.advanced').addClass('activate');
				} else {
					tabs.hide('blind');
				}
			}

			if (tabs.find('li').length < 2) {
				buttons.hide();
			}

			buttons.find('.basic').on('click', function() {
				var $this = $(this);

				if (!$this.hasClass('activate')) {
					buttons.find('.btn').removeClass('activate');
					$this.addClass('activate');
					tabs.find('li:first-child > a').trigger('click');
					tabs.hide('blind');
				}
			});

			buttons.find('.advanced').click(function() {
				var $this = $(this);

				if (!$this.hasClass('activate')) {
					buttons.find('.btn').removeClass('activate');
					$this.addClass('activate');
					tabs.show('blind');
				}
			});

			tabs.find('li').on('click', function(ev) {
				ev.preventDefault();

				var $this = $(this),
					link = $this.find('a').attr('href');

				tabs.find('li').removeClass('active');
				template.find('.pill-content >').removeClass('active');

				$this.addClass('active');
				template.find(link).addClass('active');
			});
		},

		winkstartLinkForm: function(html) {
			$('input', html).bind('change.link keyup.link focus.link', function() {
				var input = $(this),
					name = input.attr('name'),
					type = input.attr('type'),
					value = input.val(),
					id = input.attr('id'),
					input_fields = $('input[name="' + name + '"]', html);

				if (input_fields.size() > 1) {
					if (type === 'checkbox') {
						input_fields = input_fields.filter('[value=' + value + ']');
						(input.attr('checked')) ? input_fields.attr('checked', 'checked') : input_fields.removeAttr('checked');
					} else {
						$.each(input_fields, function(k, v) {
							var element = $(v);

							if (element.attr('id') !== id) {
								element.val(value);
							}
						});
					}
				} else {
					input.unbind('.link');
				}
			});
		},

		getAll: function(callApiData, startKey, continueData) {
			// Warning! Method works for listed data only!
			// -- Usage:
			// self.getAll({
			//   resource: 'user.list',
			//   success: function(result){},
			//   error: function(data){},
			//   data: {
			//      someParam: someValue
			//   }
			// })

			continueData = continueData || { data:[] };
			var self = this;

			if (typeof (callApiData.resource) === 'undefined') {
				self.log('Error! Api keyword is undefined');
				return;
			}

			var requestData = $.extend({
				accountId: self.accountId,
				generateError: false
			}, callApiData.data || {});

			if (typeof (startKey) !== 'undefined') {
				requestData.startKey = startKey;
			}

			var newRequestData = {
				resource: callApiData.resource,
				data: requestData,
				success: function(response) {
					response.data = $.merge(continueData.data, response.data);
					if (response.next_start_key && startKey !== response.next_start_key) {
						self.getAll(callApiData, response.next_start_key, response);
						return;
					}

					if (typeof (callApiData.success) === 'function') {
						callApiData.success(response);
					}
				},
				error: callApiData.error || function() {}
			};

			if (self.requests.hasOwnProperty(callApiData.resource)) {
				monster.request(newRequestData);
			} else {
				self.callApi(newRequestData);
			}
		},

		/**
		 * Recursively unsets `obj`'s empty properties by mutating it
		 * @param  {Object} obj  Object to compact
		 */
		compactObject: function(obj) {
			var self = this;
			_.each(obj, function(value, key) {
				if (_.isPlainObject(value)) {
					self.compactObject(value);
				}
				if (_.isEmpty(value)) {
					_.unset(obj, key);
				}
			});
		}
	};

	return app;
});
