// Backwards-compatible protocol version.
const VERSION = 1;

// From the frontend to the relay.
struct CommandMessage {
	// The command sequence number will be repeated in responses
	// in the respective fields.
	u32 command_seq;
	union CommandData switch (enum Command {
		HELLO,
		ACTIVE,
		BUFFER_INPUT,
		BUFFER_ACTIVATE,
		PING_RESPONSE,
		PING,
		BUFFER_COMPLETE,
		BUFFER_LOG,
	} command) {
	case HELLO:
		u32 version;
		// If the version check succeeds, the client will receive
		// an initial stream of BUFFER_UPDATE, BUFFER_LINE,
		// and finally a BUFFER_ACTIVATE message.
	case ACTIVE:
		void;
	case BUFFER_INPUT:
		string buffer_name;
		string text;
	case BUFFER_ACTIVATE:
		string buffer_name;
	case PING_RESPONSE:
		u32 event_seq;

	// Only these commands may produce Event.RESPONSE, as below,
	// but any command may produce an error.
	case PING:
		void;
	case BUFFER_COMPLETE:
		string buffer_name;
		string text;
		u32 position;
	case BUFFER_LOG:
		string buffer_name;
	} data;
};

// From the relay to the frontend.
struct EventMessage {
	u32 event_seq;
	union EventData switch (enum Event {
		PING,
		BUFFER_UPDATE,
		BUFFER_RENAME,
		BUFFER_REMOVE,
		BUFFER_ACTIVATE,
		BUFFER_LINE,
		BUFFER_CLEAR,
		ERROR,
		RESPONSE,
	} event) {
	case PING:
		void;
	case BUFFER_UPDATE:
		string buffer_name;
		// These are cumulative, even for lines flushed out from buffers.
		// Updates to these values aren't broadcasted, thus handle:
		//  - BUFFER_LINE by bumping/setting them as appropriate,
		//  - BUFFER_ACTIVATE by clearing them for the previous buffer
		//    (this way, they can be used to mark unread messages).
		// Any updates received after the initial sync should be ignored.
		u32 new_messages;
		u32 new_unimportant_messages;
		bool highlighted;
	case BUFFER_RENAME:
		string buffer_name;
		string new;
	case BUFFER_REMOVE:
		string buffer_name;
	case BUFFER_ACTIVATE:
		string buffer_name;
	case BUFFER_LINE:
		string buffer_name;
		// Whether the line should also be displayed in the active buffer.
		bool leak_to_active;
		bool is_unimportant;
		bool is_highlight;
		enum Rendition {
			BARE,
			INDENT,
			STATUS,
			ERROR,
			JOIN,
			PART,
			ACTION,
		} rendition;
		// Unix timestamp in milliseconds.
		u64 when;
		// Broken-up text, with in-band formatting.
		union ItemData switch (enum Item {
			TEXT,
			RESET,
			FG_COLOR,
			BG_COLOR,
			FLIP_BOLD,
			FLIP_ITALIC,
			FLIP_UNDERLINE,
			FLIP_INVERSE,
			FLIP_CROSSED_OUT,
			FLIP_MONOSPACE,
		} kind) {
		case TEXT:
			string text;
		case RESET:
			void;
		case FG_COLOR:
			i16 color;
		case BG_COLOR:
			i16 color;
		case FLIP_BOLD:
		case FLIP_ITALIC:
		case FLIP_UNDERLINE:
		case FLIP_INVERSE:
		case FLIP_CROSSED_OUT:
		case FLIP_MONOSPACE:
			void;
		} items<>;
	case BUFFER_CLEAR:
		string buffer_name;

	// Restriction: command_seq strictly follows the sequence received
	// by the relay, across both of these replies.
	case ERROR:
		u32 command_seq;
		string error;
	case RESPONSE:
		u32 command_seq;
		union ResponseData switch (Command command) {
		case PING:
			void;
		case BUFFER_COMPLETE:
			u32 start;
			string completions<>;
		case BUFFER_LOG:
			// UTF-8, but not guaranteed.
			u8 log<>;
		} data;
	} data;
};