'use strict';

// ============================================================================
// Subpump
// It controls Twitch PubSub.
// ============================================================================

import Module from 'utilities/module';
import { FFZEvent } from 'utilities/events';

export class PubSubEvent extends FFZEvent {
	constructor(data) {
		super(data);

		this._obj = undefined;
		this._changed = false;
	}

	markChanged() {
		this._changed = true;
	}

	get topic() {
		return this.event.topic;
	}

	get message() {
		if ( this._obj === undefined )
			this._obj = JSON.parse(this.event.message);

		return this._obj;
	}

	set message(val) {
		this._obj = val;
		this._changed = true;
	}
}

export default class Subpump extends Module {

	constructor(...args) {
		super(...args);
		this.instance = null;
	}

	onEnable(tries = 0) {
		const instances = window.__Twitch__pubsubInstances;
		if ( ! instances ) {
			if ( tries > 10 )
				this.log.warn('Unable to find PubSub.');
			else
				new Promise(r => setTimeout(r, 50)).then(() => this.onEnable(tries + 1));

			return;
		}

		for(const [key, val] of Object.entries(instances))
			if ( val?._client ) {
				if ( this.instance ) {
					this.log.warn('Multiple PubSub instances detected. Things might act weird.');
					continue;
				}

				this.instance = val;
				this.hookClient(val._client);
			}

		if ( ! this.instance )
			this.log.warn('Unable to find a PubSub instance.');
	}

	hookClient(client) {
		const t = this,
			orig_message = client._onMessage;

		client._unbindPrimary(client._primarySocket);

		client._onMessage = function(e) {
			try {
				if ( e.type === 'MESSAGE' && e.data?.topic ) {
					const raw_topic = e.data.topic,
						idx = raw_topic.indexOf('.'),
						prefix = idx === -1 ? raw_topic : raw_topic.slice(0, idx),
						trail = idx === -1 ? '' : raw_topic.slice(idx + 1);

					const event = new PubSubEvent({
						prefix,
						trail,
						event: e.data
					});

					t.emit(':pubsub-message', event);
					if ( event.defaultPrevented )
						return;

					if ( event._changed )
						e.data.message = JSON.stringify(event._obj);
				}

			} catch(err) {
				this.log.error('Error processing PubSub event.', err);
			}

			return orig_message.call(this, e);
		};

		client._bindPrimary(client._primarySocket);

		const listener = client._listens,
			orig_on = listener.on,
			orig_off = listener.off;

		listener.on = function(topic, fn, ctx) {
			const has_topic = !! listener._events?.[topic],
				out = orig_on.call(this, topic, fn, ctx);

			if ( ! has_topic )
				t.emit(':add-topic', topic)

			return out;
		}

		listener.off = function(topic, fn) {
			const has_topic = !! listener._events?.[topic],
				out = orig_off.call(this, topic, fn);

			if ( has_topic && ! listener._events?.[topic] )
				t.emit(':remove-topic', topic);

			return out;
		}
	}

	inject(topic, message) {
		const listens = this.instance?._client?._listens;
		if ( ! listens )
			throw new Error('No PubSub instance available');

		listens._trigger(topic, JSON.stringify(message));
	}

	get topics() {
		const events = this.instance?._client?._listens._events;
		if ( ! events )
			return [];

		return Object.keys(events);
	}

}