import R from 'ramda';
import { observable, extendObservable, action, toJS } from 'mobx';

import { parseInput } from '../parser';
import validateContent, { transformerForType, stringRepresenterForType } from '../ingredients/validateContent';

var suggestReferenceFromTree = R.uncurryN(2, function (ingredients) {
	return R.pipe(R.chain(R.pluck('mentions')), R.tap(function (mentions) {
		return console.log('mentions for suggestion', mentions);
	}), R.unnest, R.reject(R.isNil), R.map(R.head), // Just the requested ingredient ID
	R.difference(R.__, R.pluck('id', ingredients)), // Only IDs that are yet to be used
	R.head // First pick
	);
});

function createObservableIngredientVariation(ingredient, _ref) {
	var _ref$enabled = _ref.enabled,
	    enabled = _ref$enabled === undefined ? true : _ref$enabled,
	    rawContent = _ref.rawContent;

	return observable({
		enabled: enabled,
		rawContent: rawContent,
		get result() {
			var transform = transformerForType(ingredient.type);
			return R.tryCatch(transform, R.objOf('error'))(this.rawContent);
		},
		toggleEnabled: action(function () {
			this.enabled = !this.enabled;
		}),
		adjustRawContent: action(function (adjuster) {
			this.rawContent = adjuster(this.rawContent);
		}),
		adjustContent: action(function (adjuster) {
			var result = this.result;
			if (result.content) {
				adjuster(result.content);
				this.rawContent = stringRepresenterForType(ingredient.type, result.content);
			}
		}),
		editPath: action(function (path, editor) {
			this.adjustContent(function (content) {
				var _ref2 = path.length == 1 ? [content, path[0]] : [R.path(initialPath, content), R.last(path)],
				    parent = _ref2[0],
				    key = _ref2[1];

				if (parent) {
					editor(parent, key);
				} else {
					// Warn user about incorrect key path?
				}
			});
		}),
		adjustPath: action(function (path, adjuster) {
			this.editPath(path, function (parent, key) {
				parent[key] = adjuster(parent[key]);
			});
		})
	});
}

function createObservableIngredient(target) {
	var ingredient = observable({
		id: target.id,
		type: target.type,
		addVariation: action.bound(function (variation) {
			this.variations.push(createObservableIngredientVariation(this, variation));
		}),
		addNewVariation: action.bound(function (variation) {
			this.addVariation({
				rawContent: ''
			});
		}),
		get flattenedResult() {
			var flattened = {
				type: this.type,
				variationReference: R.last(this.variations)
			};

			if (this.type == 'json') {
				flattened.content = this.variations.reduce(function (combined, variation) {
					// Use only if enabled
					if (!variation.enabled) {
						return combined;
					}
					// Skip if no content
					var result = variation.result;
					if (result.content == null) {
						return combined;
					}
					// Merge into combined JSON object
					return Object.assign(combined, result.content);
				}, {});
			} else {
				var variation = R.findLast(R.propEq('enabled', true), this.variations);
				if (variation) {
					flattened.content = variation.result.content;
				}
			}

			return flattened;
		}
	});

	extendObservable(ingredient, {
		variations: target.variations.map(R.curry(createObservableIngredientVariation)(ingredient))
	});

	return ingredient;
}

export default function createObservableStateManager() {
	var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
	    _ref3$content = _ref3.content,
	    content = _ref3$content === undefined ? '' : _ref3$content,
	    _ref3$destinationID = _ref3.destinationID,
	    destinationID = _ref3$destinationID === undefined ? 'foundation' : _ref3$destinationID,
	    _ref3$destinationDevi = _ref3.destinationDevice,
	    destinationDevice = _ref3$destinationDevi === undefined ? 'phone' : _ref3$destinationDevi,
	    _ref3$ingredients = _ref3.ingredients,
	    ingredients = _ref3$ingredients === undefined ? [] : _ref3$ingredients,
	    _ref3$scenarios = _ref3.scenarios,
	    scenarios = _ref3$scenarios === undefined ? [{}] : _ref3$scenarios,
	    _ref3$activeScenarioI = _ref3.activeScenarioIndex,
	    activeScenarioIndex = _ref3$activeScenarioI === undefined ? 0 : _ref3$activeScenarioI;

	return observable({
		content: content,
		destinationID: destinationID,
		destinationDevice: destinationDevice,
		ingredients: ingredients.map(createObservableIngredient),
		scenarios: scenarios,
		activeScenarioIndex: activeScenarioIndex,

		get json() {
			return {
				body: this.content,
				ingredients: toJS(this.ingredients)
			};
		},
		set json(_ref4) {
			var _ref4$body = _ref4.body,
			    body = _ref4$body === undefined ? '' : _ref4$body,
			    _ref4$ingredients = _ref4.ingredients,
			    ingredients = _ref4$ingredients === undefined ? [] : _ref4$ingredients;

			this.content = body;
			this.ingredients = ingredients.map(createObservableIngredient);
		},

		get contentTree() {
			return parseInput(this.content);
		},
		get activeScenario() {
			return this.scenarios[this.activeScenarioIndex];
		},
		get activeIngredients() {
			var activeScenario = this.activeScenario;
			return this.ingredients.reduce(function (object, _ref5) {
				var id = _ref5.id,
				    flattenedResult = _ref5.flattenedResult;

				object[id] = flattenedResult;
				return object;
			}, {});
		},
		activeVariationForIngredientAtIndex: function activeVariationForIngredientAtIndex(index) {
			var ingredient = this.ingredients[index];
			if (ingredient && ingredient.variations.length > 0) {
				var activeScenario = this.activeScenario;
				return ingredient.variations[R.propOr(0, ingredient.id, activeScenario)];
			}
		},

		addNewIngredient: action.bound(function () {
			var suggestedID = suggestReferenceFromTree(this.ingredients, this.contentTree);
			this.ingredients.push(createObservableIngredient({
				id: R.defaultTo('untitled', suggestedID),
				type: 'text',
				variations: [{
					rawContent: ''
				}]
			}));
		}),
		onRemoveIngredientAtIndex: action.bound(function (index) {
			this.ingredients.splice(index, 1);
		})
	});
}