"use strict";

const matchValue = require("match-value");

const operations = require("../operations");
const typeOf = require("../type-of");
const NoChange = require("./util/no-change");
const RemoveNode = require("./util/remove-node");

// FIXME: Generalize to all predicate lists?

function createHandler(type) {
	let listOperation = matchValue.literal(type, {
		anyOfExpressions: operations.anyOf,
		allOfExpressions: operations.allOf,
		anyOfConditions: operations.anyOf,
		allOfConditions: operations.allOf,
		anyOfValues: operations.anyOf,
		allOfValues: operations.allOf,
	});

	return function flattenPredicateList(list) {
		if (Array.isArray(list.items)) {
			let hasNestedPredicates = list.items.some((item) => typeOf(item) === type);
			let hasSingleItem = (list.items.length === 1); // For unnecessary anyOf/allOf wrapping, which is also handled by this optimizer
			let mustFlatten = hasNestedPredicates || hasSingleItem;

			if (mustFlatten) {
				let actualItems = [];

				function collectItemsRecursively(node) {
					for (let subItem of node.items) {
						if (typeOf(subItem) === type) {
							collectItemsRecursively(subItem);
						} else {
							actualItems.push(subItem);
						}
					}
				}

				collectItemsRecursively(list);

				if (actualItems.length === 0) {
					// FIXME: Do we want to log this as a warning? It *could* occur when items get eliminated by another optimizer, but it could also be the result of a bug...
					console.warn("Encountered 0 actual items in predicate list");
					
					return RemoveNode;
				} else if (actualItems.length === 1) {
					// Wrapping is pointless here.
					return actualItems[0];
				} else {
					return listOperation(actualItems);
				}
			} else {
				return NoChange;
			}
		} else {
			// Ignore placeholders, SQL fragments, etc.
			return NoChange;
		}
	};
}

module.exports = {
	name: "flatten-predicate-lists",
	category: [ "readability" ],
	visitors: {
		allOfExpressions: createHandler("allOfExpressions"),
		anyOfExpressions: createHandler("anyOfExpressions"),
		allOfConditions: createHandler("allOfConditions"),
		anyOfConditions: createHandler("anyOfConditions"),
		allOfValues: createHandler("allOfValues"),
		anyOfValues: createHandler("anyOfValues"),
	}
};
