"use strict";
// FIXME: own package

const required = require("@validatem/required");
const anyProperty = require("@validatem/any-property");
const arrayOf = require("@validatem/array-of");
const isString = require("@validatem/is-string");
const isInteger = require("@validatem/is-integer");
const isBoolean = require("@validatem/is-boolean");
const anything = require("@validatem/anything");

const isEvent = require("../is-event");
const isPresenceEvent = require("../is-presence-event");
const isStateEvent = require("../is-state-event");
const isDeviceEvent = require("../is-device-event");
const isStrippedEvent = require("../is-stripped-event");
const isTimelineEvent = require("../is-timeline-event");
const isRoomID = require("../is-room-id");
const isMatrixID = require("../is-matrix-id");

const optionalObject = require("../optional-object");
const optionalArray = require("../optional-array");

module.exports = function isSyncResponse(strict = false) {
	// FIXME: Expand validation rules affected by 'strict' setting? eg. allowing extra properties in the sync response itself
	let isStateList = arrayOf([ required, isStateEvent(strict) ]);
	let isEventList = arrayOf([ required, isEvent(strict) ]);
	let isPresenceEventList = arrayOf([ required, isPresenceEvent(strict) ]);
	let isStrippedEventList = arrayOf([ required, isStrippedEvent(strict) ]);
	let isDeviceEventList = arrayOf([ required, isDeviceEvent(strict) ]);
	let isTimelineList = arrayOf([ required, isTimelineEvent(strict) ]);
	
	return {
		next_batch: [ required, isString ],
		// FIXME: also optionalObject for `rooms`
		rooms: {
			join: optionalObject(anyProperty({
				key: [ required, isRoomID ],
				value: {
					summary: optionalObject({
						"m.heroes": arrayOf([ required, isString ]),
						"m.joined_member_count": isInteger,
						"m.invited_member_count": isInteger,
					}),
					// NOTE: Despite what the spec currently says, state.events *can* contain membership events when the timeline isn't limited, when lazy-loading is enabled
					state: optionalObject({
						events: optionalArray(isStateList)
					}),
					timeline: optionalObject({
						events: optionalArray(isTimelineList),
						limited: isBoolean,
						prev_batch: isString
					}),
					ephemeral: optionalObject({
						events: optionalArray(isEventList)
					}),
					account_data: optionalObject({
						events: optionalArray(isEventList)
					}),
					unread_notifications: {
						highlight_count: isInteger,
						notification_count: isInteger
					},
					// FIXME: expose the below
					"org.matrix.msc2654.unread_count": [ isInteger ], // NOTE: https://github.com/matrix-org/matrix-doc/pull/2654
				}
			})),
			invite: optionalObject(anyProperty({
				key: [ required, isRoomID ],
				value: {
					// NOTE: This state needs to be maintained separately from known room state (see spec). FIXME: Represent this in the event list output?
					invite_state: optionalObject({
						events: optionalArray(isStrippedEventList)
					})
				}
			})),
			leave: optionalObject(anyProperty({
				key: [ required, isRoomID ],
				value: {
					state: optionalObject({
						events: optionalArray(isStateList)
					}),
					timeline: optionalObject({
						events: optionalArray(isTimelineList),
						limited: isBoolean,
						prev_batch: isString
					}),
					account_data: optionalObject({
						events: optionalArray(isEventList)
					}),
				}
			}))
		},
		presence: optionalObject({
			events: optionalArray(isPresenceEventList)
		}),
		account_data: optionalObject({
			events: optionalArray(isEventList)
		}),
		to_device: optionalObject({
			events: optionalArray(isDeviceEventList)
		}),
		device_lists: optionalObject({
			changed: arrayOf([ required, isMatrixID ]),
			left: arrayOf([ required, isMatrixID ])
		}),
		device_one_time_keys_count: optionalObject(anyProperty({
			key: [ required, isString ], // algorithm name
			value: [ required, isInteger ] // key count
		})),
		groups: anything, // NOTE: Non-standard
		// TODO: Validate algorithm names below?
		"org.matrix.msc2732.device_unused_fallback_key_types": optionalArray(arrayOf([ required, isString ])), // NOTE: https://github.com/matrix-org/matrix-doc/pull/2732
	};
};
