import { EMPTY_STRING } from '../constants/constants.js';
import { MelObject } from '../mel_object.js';
import { Top } from '../top.js';
export { ChatSingleton as Chat, Room, Chat as _Chat };

/**
 * @typedef ChatStatus
 * @property {string} status
 * @property {string} message
 */

class Chat extends MelObject {
	constructor() {
		super();
		this._init();
		let raw = this.load('tchat') ?? this.load('ariane') ?? {};
		raw.lastRoom = Room.from_event(raw.lastRoom);
		raw.unreads = Unreads.from_local(raw.unreads);
		raw.unreads.onupdate.push(() => {
			this.save_state();
		});

		Object.defineProperties(this, {
			lastRoom: {
				get: function () {
					return raw?.lastRoom ?? new InvalidRoom();
				},
				set: value => {
					const tmp_room = this.connectors.room?.connect?.(value);
					if (JSON.stringify(raw?.lastRoom) !== JSON.stringify(tmp_room)) {
						raw.lastRoom = tmp_room;
						this.save_state();
					}
				},
				configurable: true,
			},
			status: {
				get: function () {
					return raw?.status ?? EMPTY_STRING;
				},
				set: value => {
					const tmp_status = this.connectors.status?.connect?.(value) ?? value;
					if (raw?.status !== tmp_status) {
						raw.status = tmp_status;
						this.save_state();
					}
				},
				configurable: true,
			},
			message_status: {
				get: function () {
					return raw?.message ?? EMPTY_STRING;
				},
				set: value => {
					if (raw?.message !== value) {
						raw.message = value;
						this.save_state();
					}
				},
			},
			unreads: {
				get: function () {
					return raw.unreads;
				},
				configurable: true,
			},
		});
	}

	_init() {
		/**
		 * Dernier canal
		 * @type {Room}
		 */
		this.lastRoom = new InvalidRoom();
		/**
		 * Status en cours sur ariane
		 * @type {string}
		 */
		this.status = EMPTY_STRING;
		/**
		 * Liste des mentions
		 * @type {Unreads}
		 */
		this.unreads = null;

		/**
		 * Message en cours d'ariane
		 * @type {String}
		 */
		this.message_status = EMPTY_STRING;

		this.connectors = {
			status: null,
			unread: null,
			room: null,
		};

		return this;
	}

	setStatusConnector(connector) {
		this.connectors.status = connector;
		return this;
	}

	setUnreadsConnector(connector) {
		this.connectors.unread = connector;
		this.unreads.connector = this.connectors.unread;
		return this;
	}

	setRoomConnector(connector) {
		this.connectors.room = connector;
		return this;
	}

	isOnline() {
		return 'online' === this.status;
	}

	isAway() {
		return 'away' === this.status;
	}

	isBusy() {
		return 'busy' === this.status;
	}

	isOffline() {
		return 'offline' === this.status;
	}

	/**
	 *
	 * @returns {Promise<ChatStatus>}
	 */
	async get_status_from_server() {
		let status_datas = {
			status: undefined,
			message: EMPTY_STRING,
		};
		await mel_metapage.Functions.get(
			mel_metapage.Functions.url('discussion', 'get_status'),
			{},
			datas => {
				if ('string' === typeof datas) datas = JSON.parse(datas);

				status_datas.status = datas.content.status;
				status_datas.message = datas.content.message || EMPTY_STRING;

				this.status = status_datas.status;
				this.message_status = status_datas.message;
			},
		);

		return status_datas;
	}

	async set_status_to_server(status, message) {
		await mel_metapage.Functions.post(
			mel_metapage.Functions.url('discussion', 'set_status'),
			{
				_st: status,
				_msg: message,
			},
			datas => {
				//if ("string" === typeof datas) datas = JSON.parse(datas);
				this.status = status;
				this.message_status = message;
			},
		);
	}

	save_state() {
		const tmp = {
			lastRoom: this.lastRoom.save(),
			unreads: this.unreads.save(),
			status: this.status,
			message: this.message_status,
		};
		this.save('tchat', tmp);
	}
}

var ChatSingleton = {};

Object.defineProperties(ChatSingleton, {
	Instance: {
		get: function () {
			if (!Top.has('chatSingleton')) Top.add('chatSingleton', new Chat());
			return Top.get('chatSingleton');
		},
		configurable: false,
	},
});

class Room {
	constructor(name, public_) {
		this.name = name;
		this.public = public_;
	}

	isValid() {
		return true;
	}

	static from_event(event) {
		if (!event?.name) return new InvalidRoom();
		else {
			let isPublic = null;

			if (!!event.public || event.public === false) isPublic = event.public;
			else {
				switch (event.t) {
					case 'c':
						isPublic = true;
						break;
					case 'p':
						isPublic = false;
						break;
					default:
						return new InvalidRoom();
				}
			}

			return new Room(event.name, isPublic);
		}
	}

	save() {
		return {
			name: this.name,
			public: this.public,
		};
	}
}

export class InvalidRoom extends Room {
	constructor() {
		super(null, null);
	}

	isValid() {
		return false;
	}

	save() {
		return null;
	}
}

export class Unreads {
	constructor(datas) {
		this.datas = datas;
		this._unreads = false;
		this.onupdate = new MelEvent();
		this.connector = null;
	}

	update(key, value) {
		const current_value = !this.connector
			? value
			: this.connector.connect(key, value)?.value;

		if (this.datas[key] !== current_value) {
			this.datas[key] = current_value;
			this.onupdate.call();
		}
		return this;
	}

	updateAll(value) {
		if (!this.connector) this.datas = value;
		else this.datas = this.connector.connect(value).datas ?? value;
		this.onupdate.call();
		return this;
	}

	get(key) {
		return this.datas[key];
	}

	setHaveUnreads(val) {
		const value = !this.connector
			? val
			: this.connector.connect(null, val).unreads;

		if (value !== this._unreads) {
			this._unreads = value;
			this.onupdate.call();
		}

		this.onupdate.call();

		return this;
	}

	haveUnreads() {
		return this._unreads;
	}

	haveMention() {
		return this.count();
	}

	*getHaveMentions_generator() {
		for (const iterator of this) {
			const { key, value } = iterator;

			if (value !== 0 && key !== 'haveSomeUnreads') yield iterator;
		}
	}

	getHaveMentions() {
		let mentions = {};
		for (const iterator of this.getHaveMentions_generator()) {
			const { key, value } = iterator;
			mentions[key] = value;
		}

		return mentions;
	}

	count() {
		return Enumerable.from(this)
			.where(x => x.value !== true && x.value !== false && x.value > 0)
			.count();
	}

	save() {
		return {
			datas: this.datas,
			_unreads: this._unreads,
		};
	}

	*[Symbol.iterator]() {
		for (const key in this.datas) {
			if (Object.hasOwnProperty.call(this.datas, key)) {
				const element = this.datas[key];
				yield { key, value: element };
			}
		}
		yield { key: 'haveSomeUnreads', value: this._unreads };
	}

	static from_local(value) {
		let u = new Unreads(value?.datas ?? {});
		u._unreads = value?._unreads ?? false;
		return u;
	}
}