"use strict";

const defaultValue = require("default-value");
const flatten = require("flatten");
const addCommonFields = require("../event-add-common-fields");
const mapMaybeRedacted = require("../map-maybe-redacted");
const numberDerivedEvents = require("../number-derived-events");

function mapProfile(eventContent) {
	return {
		avatar: normalizeProfileField(eventContent.avatar_url),
		displayName: normalizeProfileField(eventContent.displayname)
	};
}

function normalizeProfileField(value) {
	if (value === null || value === undefined) {
		// We make this an *explicit* null, as this makes it easier for the consuming code to distinguish between "this user has unset the profile field" and "we just haven't seen a profile field yet".
		return null;
	} else {
		return value;
	}
}

module.exports = function mapMemberEvent(event, _context) {
	let previousContent = defaultValue(event.unsigned.prev_content, { membership: "leave" });

	let oldState = previousContent.membership;
	let newState = event.content.membership;
	let oldProfile = mapProfile(previousContent);
	let newProfile = mapProfile(event.content);

	let voluntaryChange = (event.state_key === event.sender);

	let transitions = {
		invite: {
			invite: null,
			join: "_userAcceptedInviteAndJoined",
			leave: (voluntaryChange)
				? "userRejectedInvite"
				: "userInviteWasRevoked",
			ban: "userWasBanned"
		},
		join: {
			join: "_userChangedProfile",
			leave: (voluntaryChange)
				? "userLeft"
				: "userWasKicked",
			ban: "_userWasKickedAndBanned"
		},
		leave: {
			invite: "userWasInvited",
			join: "userJoined",
			leave: null,
			ban: "userWasBanned"
		},
		ban: {
			leave: "userWasUnbanned",
			ban: null
		}
	};

	// userChangedProfile -> userChangedDisplayName / userChangedAvatar
	// use prev_content to determine diff

	let typesWithProfileData = new Set([ "_userChangedProfile", "userJoined", "_userAcceptedInviteAndJoined" ]);
	
	if (transitions[oldState] !== undefined && transitions[oldState][newState] !== undefined) {
		let type = transitions[oldState][newState];
		let hasProfileData = typesWithProfileData.has(type);
		let deltaEvents = [];

		if (hasProfileData) {
			if (oldProfile.avatar != newProfile.avatar) {
				deltaEvents.push({
					type: "userChangedAvatar",
					user: event.state_key,
					sender: event.sender,
					... mapMaybeRedacted(event, () => {
						return {
							url: newProfile.avatar,
							previousURL: oldProfile.avatar
						};
					})
				});
			}

			if (oldProfile.displayName != newProfile.displayName) {
				deltaEvents.push({
					type: "userChangedDisplayName",
					user: event.state_key,
					sender: event.sender,
					... mapMaybeRedacted(event, () => {
						return {
							name: newProfile.displayName,
							previousName: oldProfile.displayName
						};
					})
				});
			}
		}

		// We ignore any "no change" transitions, as well as any profile changes (since those will have been handled in full above)
		if (type !== null && type !== "_userChangedProfile") {
			let membershipFields = {
				user: event.state_key,
				sender: event.sender,
				... mapMaybeRedacted(event, () => {
					return {
						reason: event.content.reason,
						// FIXME: is_direct flag, translate into "is direct message" event if prev_content does not contain it
						// FIXME: third_party_invite.display_name
					};
				})
			};

			// NOTE: We have a few special cases below, where certain membership changes result in multiple logical actions. To leave it up to the consumer whether to care about the cause of eg. a kick or join, we represent the logical actions as individual events, annotating them with metadata so that a cause-interested client can ignore the implicitly-generated events.

			if (type === "_userWasKickedAndBanned") {
				deltaEvents.push([
					addCommonFields(event, {
						type: "userWasKicked",
						causedByBan: true,
						... membershipFields
					}),
					addCommonFields(event, {
						type: "userWasBanned",
						... membershipFields
					}),
				]);
			} else if (type === "_userAcceptedInviteAndJoined") {
				deltaEvents.push([
					addCommonFields(event, {
						type: "userAcceptedInvite",
						... membershipFields
					}),
					addCommonFields(event, {
						type: "userJoined",
						causedByInvite: true,
						... membershipFields
					}),
				]);
			} else {
				deltaEvents.push(addCommonFields(event, {
					type: type,
					... membershipFields
				}));
			}
		}

		let flattenedDeltaEvents = flatten(deltaEvents);

		numberDerivedEvents(event.event_id, flattenedDeltaEvents);

		return flattenedDeltaEvents;
	} else {
		// FIXME: Error type
		throw new Error(`Membership transition not allowed: ${oldState} => ${newState}`);
	}
};
