"use strict";

const matchValue = require("match-value");
const defaultValue = require("default-value");
const syncpipe = require("syncpipe");
const splitFilter = require("split-filter");
const flatten = require("flatten");
const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME

function parsePredicate(predicate) {
	let match = /^(>|>=|<|<=|==)?([0-9]+)$/.exec(predicate);

	if (match != null) {
		return {
			operator: defaultValue(match[1], "=="),
			value: match[2]
		};
	} else {
		unreachable("Failed to parse predicate");
	}
}

function mapCondition(condition) {
	if (condition.kind === "event_match") {
		return {
			type: "matchEventField",
			field: condition.key,
			pattern: condition.pattern
		};
	} else if (condition.kind === "contains_display_name") {
		return {
			type: "matchDisplayName"
		};
	} else if (condition.kind === "room_member_count") {
		let { operator, value } = parsePredicate(condition.is);

		return {
			type: "matchRoomMemberCount",
			operator: operator,
			value: value
		};
	} else if (condition.kind === "sender_notification_permission") {
		return {
			type: "requireEventPowerLevel",
			notificationType: condition.key
		};
	} else {
		// Spec: "Unrecognised conditions MUST NOT match any events, effectively making the push rule disabled."
		// FIXME: Log warning?
		return {
			type: "neverMatch"
		};
	}
}

function mapAction(action) {
	if (action === "notify") {
		return {
			type: "notification"
		};
	} else if (action === "dont_notify") {
		return {
			type: "preventNotification"
		};
	} else if (action === "coalesce") {
		return {
			type: "digestNotification"
		};
	} else if (action.set_tweak != null) {
		let key = action.set_tweak;

		return {
			type: "setTweak",
			key: key,
			value: matchValue(key, {
				sound: () => (action.value === "default") ? true : action.value,
				highlight: () => defaultValue(action.value, true),
				_: () => action.value
			})
		};
	} else {
		unreachable(`Unrecognized action type`);
	}
}

function mapRule(rule) {
	let mappedActions = rule.actions.map(mapAction);
	let [ tweaks, actualActions ] = splitFilter(mappedActions, (action) => action.type === "setTweak");

	return {
		id: rule.rule_id,
		isDefault: rule.default,
		isEnabled: rule.enabled,
		actions: actualActions,
		// FIXME: Clearly document that these 'options' are called 'tweaks' on the push gateway side!
		notificationOptions: Object.fromEntries(tweaks.map(({ key, value }) => [ key, value ]))
	};
}

function mapRuleset(rules, key) {
	if (rules == null) {
		return [];
	} else if (key === "content") {
		return rules.map((rule) => {
			return {
				... mapRule(rule),
				conditions: [{
					type: "matchContent",
					pattern: rule.pattern
				}]
			};
		});
	} else if (key === "room") {
		return rules.map((rule) => {
			return {
				... mapRule(rule),
				conditions: [{
					type: "matchRoom",
					roomID: rule.rule_id
				}]
			};
		});
	} else if (key === "sender") {
		return rules.map((rule) => {
			return {
				... mapRule(rule),
				conditions: [{
					type: "matchSender",
					userID: rule.rule_id
				}]
			};
		});
	} else if (key === "override" || key === "underride") {
		return rules.map((rule) => {
			return {
				... mapRule(rule),
				conditions: rule.conditions.map(mapCondition)
			};
		});
	} else {
		throw unreachable("Unrecognized ruleset key");
	}
}

function mapRulesets(rulesets) {
	if (rulesets != null) {
		return syncpipe([ "override", "content", "room", "sender", "underride" ], [
			(_) => _.map((key) => mapRuleset(rulesets[key], key)),
			(_) => _.filter((ruleset) => ruleset.length > 0),
			(_) => flatten(_)
		]);
	} else {
		return [];
	}
}

module.exports = function mapPushRulesEvent(event, _context) {
	return {
		type: "pushRulesChanged",
		rulesets: mapRulesets(event.content.global)
	};
};
