import { by_lurid } from '@andyburke/fsdb/organizers';
import { FSDB_COLLECTION } from '@andyburke/fsdb';
import { FSDB_INDEXER_SYMLINKS } from '@andyburke/fsdb/indexers';
import { EMOJIS } from '../public/js/emojis/en.ts';

/**
 * @typedef {object} TIMESTAMPS
 * @property {string} created		when the event was created
 * @property {string} updated		when the event was last updated
 */

/**
 * Event
 *
 * @property {string} id					- lurid
 * @property {string} creator_id			- id of the source user
 * @property {string} type					- event type
 * @property {string} [parent_id]			- optional parent event id
 * @property {string} [channel]				- optional channel
 * @property {string} [topic]				- optional topic
 * @property {string[]} [tags]				- optional tags
 * @property {Record<string,any>} [data]	- optional data payload of the event
 * @property {TIMESTAMPS} timestamps		- timestamps that will be set by the server
 */
export type EVENT = {
	id: string;
	creator_id: string;
	type: string;
	parent_id?: string;
	channel?: string;
	topic?: string;
	tags?: string[];
	data?: Record<string, any>;
	timestamps: {
		created: string;
		updated: string;
	};
};

// TODO: separate out these different validators somewhere?
export function VALIDATE_EVENT(event: EVENT) {
	const errors: any[] = [];

	const [type, id] = (event.id ?? '').split(':', 2);

	if (typeof type !== 'string' || type.length === 0) {
		errors.push({
			cause: 'missing_event_type_in_id',
			message: 'An event must have a type that is also encoded into its id, eg: chat:able-fish-wife...'
		});
	}

	if (typeof id !== 'string' || id.length !== 49) {
		errors.push({
			cause: 'invalid_event_id',
			message: 'An event must have a type and a lurid id, eg: chat:able-fish-gold-wing-trip-form-seed-cost-rope-wife'
		});
	}

	switch (event.type) {
		case 'chat':
			if (event.data?.message?.length <= 0) {
				errors.push({
					cause: 'chat_message_missing',
					message: 'A chat message event cannot be empty.'
				});
			}
			break;
		case 'post':
			if (event.data?.subject?.length <= 0) {
				errors.push({
					cause: 'post_missing_subject',
					message: 'A post cannot have an empty subject.'
				});
			}
			break;
		case 'blurb':
			if (event.data?.blurb?.length <= 0) {
				errors.push({
					cause: 'blurb_missing',
					message: 'A blurb cannot be empty.'
				});
			} else if (event.data?.blurb?.length > 2 ** 8) {
				errors.push({
					cause: 'blurb_length_limit_exceeded',
					message: 'A blurb cannot be longer than 256 characters.'
				});
			}
			break;
		case 'essay':
			if (event.data?.title?.length <= 0) {
				errors.push({
					cause: 'essay_title_missing',
					message: 'An essay must have a title.'
				});
			} else if (event.data?.title?.length > 2 ** 7) {
				errors.push({
					cause: 'essay_title_length_limit_exceeded',
					message: 'An essay title cannot be longer than 128 characters.'
				});
			} else if (event.data?.essay?.length <= 0) {
				errors.push({
					cause: 'essay_missing',
					message: 'An essay cannot be empty.'
				});
			} else if (event.data?.essay?.length > 2 ** 16) {
				errors.push({
					cause: 'essay_length_limit_exceeded',
					message: 'An essay cannot be longer than 65536 characters - that would be a screed, which we do not yet support.'
				});
			}
			break;
		case 'presence':
			break;
		case 'reaction':
			if (typeof event.parent_id !== 'string') {
				errors.push({
					cause: 'reaction_missing_parent_id',
					message: 'A reaction must have a parent_id that refers to another event.'
				});
			} else if (typeof event.data?.reaction !== 'string') {
				errors.push({
					cause: 'reaction_must_be_an_emoji',
					message: 'A reaction event must have a `data.reaction` that is an emoji.'
				});
			} else if (typeof EMOJIS.MAP[event.data?.reaction] === 'undefined') {
				errors.push({
					cause: 'reaction_must_be_an_emoji',
					message: 'A reaction event must have a `data.reaction` that is an emoji.'
				});
			}
			break;
		case 'test':
			if (Deno.env.get('DENO_ENV') !== 'test') {
				errors.push({
					cause: 'unknown_event_type',
					message: 'Event types allowed: ' + ['chat', 'post', 'blurb', 'essay', 'reaction'].join(', ')
				});
			}
			break;
		default:
			errors.push({
				cause: 'unknown_event_type',
				message: 'Event types allowed: ' + ['chat', 'post', 'blurb', 'essay', 'reaction'].join(', ')
			});
			break;
	}

	return errors.length ? errors : undefined;
}

const EVENT_ID_EXTRACTOR = /^(?<event_type>.*):(?<event_id>.*)$/;

function smart_event_id_organizer(id: string) {
	const [event_type, event_id] = id.split(':', 2);
	const event_dirs = by_lurid(event_id).slice(0, -1);
	return [event_type, ...event_dirs, `${id}.json`];
}

export const EVENTS = new FSDB_COLLECTION<EVENT>({
	name: `events`,
	id_field: 'id',
	organize: (id) => {
		EVENT_ID_EXTRACTOR.lastIndex = 0;

		const groups: Record<string, string> | undefined = EVENT_ID_EXTRACTOR.exec(id ?? '')?.groups;

		if (!groups) {
			throw new Error('Could not parse event id: ' + id);
		}

		const event_type = groups.event_type;
		const event_id = groups.event_id;

		return [
			event_type,
			event_id.slice(0, 14),
			event_id.slice(0, 34),
			event_id,
			`${id}.json`
		];
	},
	indexers: {
		creator_id: new FSDB_INDEXER_SYMLINKS<EVENT>({
			name: 'creator_id',
			field: 'creator_id',
			to_many: true,
			organize: by_lurid
		}),

		parent_id: new FSDB_INDEXER_SYMLINKS<EVENT>({
			name: 'parent_id',
			field: 'parent_id',
			to_many: true,
			organize: smart_event_id_organizer
		}),

		channel: new FSDB_INDEXER_SYMLINKS<EVENT>({
			name: 'channel',
			field: 'channel',
			to_many: true,
			organize: (channel: string) => [channel],
			organize_id: smart_event_id_organizer
		}),

		topic: new FSDB_INDEXER_SYMLINKS<EVENT>({
			name: 'topic',
			field: 'topic',
			to_many: true,
			organize: (topic: string) => [topic],
			organize_id: smart_event_id_organizer
		}),

		tags: new FSDB_INDEXER_SYMLINKS<EVENT>({
			name: 'tags',
			get_values_to_index: (event: EVENT): string[] => {
				return (event.tags ?? []).map((tag: string) => tag.toLowerCase());
			},
			to_many: true,
			organize: (tag: string) => tag.length > 3 ? [tag.substring(0, 3), tag] : [tag],
			organize_id: smart_event_id_organizer
		})
	}
});
