import {LitElement, html, render, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js';
import {styles} from './tf-styles.js';

class TfMessageElement extends LitElement {
	static get properties() {
		return {
			whoami: {type: String},
			message: {type: Object},
			users: {type: Object},
			drafts: {type: Object},
			format: {type: String},
			blog_data: {type: String},
			expanded: {type: Object},
		};
	}

	static styles = styles;

	constructor() {
		super();
		let self = this;
		this.whoami = null;
		this.message = {};
		this.users = {};
		this.drafts = {};
		this.format = 'message';
		this.expanded = {};
	}

	show_reply() {
		let event = new CustomEvent('tf-draft', {
			bubbles: true,
			composed: true,
			detail: {
				id: this.message?.id,
				draft: {
					encrypt_to: this.message?.decrypted?.recps,
				},
			},
		});
		this.dispatchEvent(event);
	}

	discard_reply() {
		this.dispatchEvent(
			new CustomEvent('tf-draft', {
				bubbles: true,
				composed: true,
				detail: {id: this.id, draft: undefined},
			})
		);
	}

	show_reactions() {
		let modal = document.getElementById('reactions_modal');
		modal.users = this.users;
		modal.votes = this.message?.votes || [];
	}

	render_votes() {
		function normalize_expression(expression) {
			if (expression === 'Like' || !expression) {
				return '👍';
			} else if (expression === 'Unlike') {
				return '👎';
			} else if (expression === 'heart') {
				return '❤️';
			} else {
				return expression;
			}
		}
		if (this.message?.votes?.length) {
			return html`<div class="w3-button" @click=${this.show_reactions}>
				${(this.message.votes || []).map(
					(vote) => html`
						<span
							title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
								vote.timestamp
							)}"
						>
							${normalize_expression(vote.content.vote.expression)}
						</span>
					`
				)}
			</div>`;
		}
	}

	render_raw() {
		let raw = {
			id: this.message?.id,
			previous: this.message?.previous,
			author: this.message?.author,
			sequence: this.message?.sequence,
			timestamp: this.message?.timestamp,
			hash: this.message?.hash,
			content: this.message?.content,
			signature: this.message?.signature,
		};
		return html`<div style="white-space: pre-wrap">
			${JSON.stringify(raw, null, 2)}
		</div>`;
	}

	vote(emoji) {
		let reaction = emoji;
		let message = this.message.id;
		if (
			confirm(
				'Are you sure you want to react with ' +
					reaction +
					' to ' +
					message +
					'?'
			)
		) {
			tfrpc.rpc
				.appendMessage(this.whoami, {
					type: 'vote',
					vote: {
						link: message,
						value: 1,
						expression: reaction,
					},
				})
				.catch(function (error) {
					alert(error?.message);
				});
		}
	}

	react(event) {
		emojis.picker((x) => this.vote(x), null, this.whoami);
	}

	show_image(link) {
		let div = document.createElement('div');
		div.style.left = 0;
		div.style.top = 0;
		div.style.width = '100%';
		div.style.height = '100%';
		div.style.position = 'fixed';
		div.style.background = '#000';
		div.style.zIndex = 100;
		div.style.display = 'grid';
		let img = document.createElement('img');
		img.src = link;
		img.style.maxWidth = '100%';
		img.style.maxHeight = '100%';
		img.style.display = 'block';
		img.style.margin = 'auto';
		img.style.objectFit = 'contain';
		img.style.width = '100%';
		div.appendChild(img);
		function image_close(event) {
			document.body.removeChild(div);
			window.removeEventListener('keydown', image_close);
		}
		div.onclick = image_close;
		window.addEventListener('keydown', image_close);
		document.body.appendChild(div);
	}

	body_click(event) {
		if (event.srcElement.tagName == 'IMG') {
			this.show_image(event.srcElement.src);
		} else if (
			event.srcElement.tagName == 'DIV' &&
			event.srcElement.classList.contains('img_caption')
		) {
			let next = event.srcElement.nextSibling;
			if (next.style.display == 'block') {
				next.style.display = 'none';
			} else {
				next.style.display = 'block';
			}
		}
	}

	render_mention(mention) {
		if (!mention?.link || typeof mention.link != 'string') {
			return html` <pre>${JSON.stringify(mention)}</pre>`;
		} else if (
			mention?.link?.startsWith('&') &&
			mention?.type?.startsWith('image/')
		) {
			return html`
				<img
					src=${'/' + mention.link + '/view'}
					style="max-width: 128px; max-height: 128px"
					title=${mention.name}
					@click=${() => this.show_image('/' + mention.link + '/view')}
				/>
			`;
		} else if (
			mention.link?.startsWith('&') &&
			mention.name?.startsWith('audio:')
		) {
			return html`
				<audio controls style="height: 32px">
					<source src=${'/' + mention.link + '/view'}></source>
				</audio>
			`;
		} else if (
			mention.link?.startsWith('&') &&
			mention.name?.startsWith('video:')
		) {
			return html`
				<video controls style="max-height: 240px; max-width: 128px">
					<source src=${'/' + mention.link + '/view'}></source>
				</video>
			`;
		} else if (
			mention.link?.startsWith('&') &&
			mention?.type === 'application/tildefriends'
		) {
			return html` <a href=${`/${mention.link}/`}>😎 ${mention.name}</a>`;
		} else if (mention.link?.startsWith('%') || mention.link?.startsWith('@')) {
			return html` <a href=${'#' + encodeURIComponent(mention.link)}
				>${mention.name}</a
			>`;
		} else if (mention.link?.startsWith('#')) {
			return html` <a href=${'#q=' + encodeURIComponent(mention.link)}
				>${mention.link}</a
			>`;
		} else if (
			Object.keys(mention).length == 2 &&
			mention.link &&
			mention.name
		) {
			return html` <a href=${`/${mention.link}/view`}>${mention.name}</a>`;
		} else {
			return html` <pre style="white-space: pre-wrap">
${JSON.stringify(mention, null, 2)}</pre
			>`;
		}
	}

	render_mentions() {
		let mentions = this.message?.content?.mentions || [];
		mentions = mentions.filter(
			(x) => this.message?.content?.text?.indexOf(x.link) === -1
		);
		if (mentions.length) {
			let self = this;
			return html`
				<fieldset style="padding: 0.5em; border: 1px solid black">
					<legend>Mentions</legend>
					${mentions.map((x) => self.render_mention(x))}
				</fieldset>
			`;
		}
	}

	total_child_messages(message) {
		if (!message.child_messages) {
			return 0;
		}
		let total = message.child_messages.length;
		for (let m of message.child_messages) {
			total += this.total_child_messages(m);
		}
		return total;
	}

	set_expanded(expanded, tag) {
		this.dispatchEvent(
			new CustomEvent('tf-expand', {
				bubbles: true,
				composed: true,
				detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
			})
		);
	}

	toggle_expanded(tag) {
		this.set_expanded(
			!this.expanded[(this.message.id || '') + (tag || '')],
			tag
		);
	}

	render_children() {
		let self = this;
		if (this.message.child_messages?.length) {
			if (!this.expanded[this.message.id]) {
				return html`<button
					class="w3-button w3-theme-d1"
					@click=${() => self.set_expanded(true)}
				>
					+ ${this.total_child_messages(this.message) + ' More'}
				</button>`;
			} else {
				return html`<button
						class="w3-button w3-theme-d1"
						@click=${() => self.set_expanded(false)}
					>
						Collapse</button
					>${(this.message.child_messages || []).map(
						(x) =>
							html`<tf-message
								.message=${x}
								whoami=${this.whoami}
								.users=${this.users}
								.drafts=${this.drafts}
								.expanded=${this.expanded}
							></tf-message>`
					)}`;
			}
		}
	}

	render_channels() {
		let content = this.message?.content;
		if (this?.messsage?.decrypted?.type == 'post') {
			content = this.message.decrypted;
		}
		let channels = [];
		if (typeof content.channel === 'string') {
			channels.push(`#${content.channel}`);
		}
		if (Array.isArray(content.mentions)) {
			for (let mention of content.mentions) {
				if (typeof mention?.link === 'string' && mention.link.startsWith('#')) {
					channels.push(mention.link);
				}
			}
		}
		return channels.map((x) => html`<tf-tag tag=${x}></tf-tag>`);
	}

	render() {
		let content = this.message?.content;
		if (this.message?.decrypted?.type == 'post') {
			content = this.message.decrypted;
		}
		let class_background = this.message?.decrypted
			? 'w3-pale-red'
			: 'w3-theme-d4';
		let self = this;
		let raw_button;
		switch (this.format) {
			case 'raw':
				if (content?.type == 'post' || content?.type == 'blog') {
					raw_button = html`<button
						class="w3-button w3-theme-d1"
						@click=${() => (self.format = 'md')}
					>
						Markdown
					</button>`;
				} else {
					raw_button = html`<button
						class="w3-button w3-theme-d1"
						@click=${() => (self.format = 'message')}
					>
						Message
					</button>`;
				}
				break;
			case 'md':
				raw_button = html`<button
					class="w3-button w3-theme-d1"
					@click=${() => (self.format = 'message')}
				>
					Message
				</button>`;
				break;
			case 'decrypted':
				raw_button = html`<button
					class="w3-button w3-theme-d1"
					@click=${() => (self.format = 'raw')}
				>
					Raw
				</button>`;
				break;
			default:
				if (this.message.decrypted) {
					raw_button = html`<button
						class="w3-button w3-theme-d1"
						@click=${() => (self.format = 'decrypted')}
					>
						Decrypted
					</button>`;
				} else {
					raw_button = html`<button
						class="w3-button w3-theme-d1"
						@click=${() => (self.format = 'raw')}
					>
						Raw
					</button>`;
				}
				break;
		}
		function small_frame(inner) {
			let body;
			return html`
				<div
					class="w3-card-4 w3-theme-d4 w3-border-theme"
					style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
				>
					<tf-user id=${self.message.author} .users=${self.users}></tf-user>
					<span style="padding-right: 8px"
						><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(
							self.message.timestamp
						).toLocaleString()}</span
					>
					${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
					${self.render_votes()}
					${(self.message.child_messages || []).map(
						(x) => html`
							<tf-message
								.message=${x}
								whoami=${self.whoami}
								.users=${self.users}
								.drafts=${self.drafts}
								.expanded=${self.expanded}
							></tf-message>
						`
					)}
				</div>
			`;
		}
		if (this.message?.type === 'contact_group') {
			return html` <div
				class="w3-card-4 w3-theme-d4 w3-border-theme"
				style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
			>
				${this.message.messages.map(
					(x) =>
						html`<tf-message
							.message=${x}
							whoami=${this.whoami}
							.users=${this.users}
							.drafts=${this.drafts}
							.expanded=${this.expanded}
						></tf-message>`
				)}
			</div>`;
		} else if (this.message.placeholder) {
			return html` <div
				class="w3-card-4 w3-theme-d4 w3-border-theme"
				style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
			>
				<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
				(placeholder)
				<div>${this.render_votes()}</div>
				${(this.message.child_messages || []).map(
					(x) => html`
						<tf-message
							.message=${x}
							whoami=${this.whoami}
							.users=${this.users}
							.drafts=${this.drafts}
							.expanded=${this.expanded}
						></tf-message>
					`
				)}
			</div>`;
		} else if (typeof (content?.type === 'string')) {
			if (content.type == 'about') {
				let name;
				let image;
				let description;
				if (content.name !== undefined) {
					name = html`<div><b>Name:</b> ${content.name}</div>`;
				}
				if (content.image !== undefined) {
					image = html`
						<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
					`;
				}
				if (content.description !== undefined) {
					description = html`
						<div style="flex: 1 0 50%; overflow-wrap: anywhere">
							<div>${unsafeHTML(tfutils.markdown(content.description))}</div>
						</div>
					`;
				}
				let update =
					content.about == this.message.author
						? html`<div style="font-weight: bold">Updated profile.</div>`
						: html`<div style="font-weight: bold">
								Updated profile for
								<tf-user id=${content.about} .users=${this.users}></tf-user>.
							</div>`;
				return small_frame(html` ${update} ${name} ${image} ${description} `);
			} else if (content.type == 'contact') {
				return html`
					<div>
						<tf-user id=${this.message.author} .users=${this.users}></tf-user>
						is
						${content.blocking === true
							? 'blocking'
							: content.blocking === false
								? 'no longer blocking'
								: content.following === true
									? 'following'
									: content.following === false
										? 'no longer following'
										: '?'}
						<tf-user
							id=${this.message.content.contact}
							.users=${this.users}
						></tf-user>
					</div>
				`;
			} else if (content.type == 'post') {
				let reply =
					this.drafts[this.message?.id] !== undefined
						? html`
								<tf-compose
									whoami=${this.whoami}
									.users=${this.users}
									root=${content.root || this.message.id}
									branch=${this.message.id}
									.drafts=${this.drafts}
									@tf-discard=${this.discard_reply}
									author=${this.message.author}
								></tf-compose>
							`
						: html`
								<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
									Reply
								</button>
							`;
				let self = this;
				let body;
				switch (this.format) {
					case 'raw':
						body = this.render_raw();
						break;
					case 'md':
						body = html`<code
							style="white-space: pre-wrap; overflow-wrap: anywhere"
							>${content.text}</code
						>`;
						break;
					case 'message':
						body = unsafeHTML(tfutils.markdown(content.text));
						break;
					case 'decrypted':
						body = html`<pre
							style="white-space: pre-wrap; overflow-wrap: anywhere"
						>
${JSON.stringify(content, null, 2)}</pre
						>`;
						break;
				}
				let content_warning = html`
					<div
						class="w3-panel w3-round-xlarge w3-theme-l4"
						style="cursor: pointer"
						@click=${(x) => this.toggle_expanded(':cw')}
					>
						<p>${content.contentWarning}</p>
					</div>
				`;
				let content_html = html`
					${this.render_channels()}
					<div @click=${this.body_click}>${body}</div>
					${this.render_mentions()}
				`;
				let payload = content.contentWarning
					? self.expanded[(this.message.id || '') + ':cw']
						? html` ${content_warning} ${content_html} `
						: content_warning
					: content_html;
				let is_encrypted = this.message?.decrypted
					? html`<span style="align-self: center">🔓</span>`
					: undefined;
				return html`
					<style>
						code {
							white-space: pre-wrap;
							overflow-wrap: break-word;
						}
						div {
							overflow-wrap: anywhere;
						}
						img {
							max-width: 100%;
							height: auto;
							display: block;
						}
					</style>
					<div
						class="w3-card-4 ${class_background} w3-border-theme"
						style="margin-top: 8px; padding: 16px"
					>
						<div style="display: flex; flex-direction: row">
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
							${is_encrypted}
							<span style="flex: 1"></span>
							<span style="padding-right: 8px"
								><a target="_top" href=${'#' + self.message.id}>%</a>
								${new Date(this.message.timestamp).toLocaleString()}</span
							>
							<span>${raw_button}</span>
						</div>
						${payload} ${this.render_votes()}
						<p>
							${reply}
							<button class="w3-button w3-theme-d1" @click=${this.react}>
								React
							</button>
						</p>
						${this.render_children()}
					</div>
				`;
			} else if (content.type === 'issue') {
				let is_encrypted = this.message?.decrypted
					? html`<span style="align-self: center">🔓</span>`
					: undefined;
				return html`
					<style>
						code {
							white-space: pre-wrap;
							overflow-wrap: break-word;
						}
						div {
							overflow-wrap: anywhere;
						}
						img {
							max-width: 100%;
							height: auto;
							display: block;
						}
					</style>
					<div
						class="w3-card-4 ${class_background} w3-border-theme"
						style="margin-top: 8px; padding: 16px"
					>
						<div style="display: flex; flex-direction: row">
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
							${is_encrypted}
							<span style="flex: 1"></span>
							<span style="padding-right: 8px"
								><a target="_top" href=${'#' + self.message.id}>%</a>
								${new Date(this.message.timestamp).toLocaleString()}</span
							>
							<span>${raw_button}</span>
						</div>
						${content.text} ${this.render_votes()}
						<p>
							<button class="w3-button w3-theme-d1" @click=${this.react}>
								React
							</button>
						</p>
						${this.render_children()}
					</div>
				`;
			} else if (content.type === 'blog') {
				let self = this;
				tfrpc.rpc.get_blob(content.blog).then(function (data) {
					self.blog_data = data;
				});
				let payload = this.expanded[(this.message.id || '') + ':blog']
					? html`<div>
							${this.blog_data
								? unsafeHTML(tfutils.markdown(this.blog_data))
								: 'Loading...'}
						</div>`
					: undefined;
				let body;
				switch (this.format) {
					case 'raw':
						body = this.render_raw();
						break;
					case 'md':
						body = content.summary;
						break;
					case 'message':
						body = html`
							<div
								style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
								@click=${(x) => self.toggle_expanded(':blog')}>
								<h2>${content.title}</h2>
								<div style="display: flex; flex-direction: row">
									<img src=/${content.thumbnail}/view></img>
									<span>${content.summary}</span>
								</div>
							</div>
							${payload}
						`;
						break;
				}
				let reply =
					this.drafts[this.message?.id] !== undefined
						? html`
								<tf-compose
									whoami=${this.whoami}
									.users=${this.users}
									root=${content.root || this.message.id}
									branch=${this.message.id}
									.drafts=${this.drafts}
									@tf-discard=${this.discard_reply}
									author=${this.message.author}
								></tf-compose>
							`
						: html`
								<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
									Reply
								</button>
							`;
				return html`
					<style>
						code {
							white-space: pre-wrap;
							overflow-wrap: break-word;
						}
						div {
							overflow-wrap: anywhere;
						}
						img {
							max-width: 100%;
							height: auto;
							display: block;
						}
					</style>
					<div
						class="w3-card-4 w3-theme-d4 w3-border-theme"
						style="margin-top: 8px; padding: 16px"
					>
						<div style="display: flex; flex-direction: row">
							<tf-user id=${this.message.author} .users=${this.users}></tf-user>
							<span style="flex: 1"></span>
							<span style="padding-right: 8px"
								><a target="_top" href=${'#' + self.message.id}>%</a>
								${new Date(this.message.timestamp).toLocaleString()}</span
							>
							<span>${raw_button}</span>
						</div>

						<div>${body}</div>
						${this.render_mentions()}
						<div>
							${reply}
							<button class="w3-button w3-theme-d1" @click=${this.react}>
								React
							</button>
						</div>
						${this.render_votes()} ${this.render_children()}
					</div>
				`;
			} else if (content.type === 'pub') {
				return small_frame(
					html` <style>
							span {
								overflow-wrap: anywhere;
							}
						</style>
						<span>
							<div>
								🍻
								<tf-user
									.users=${this.users}
									id=${content.address.key}
								></tf-user>
							</div>
							<pre>${content.address.host}:${content.address.port}</pre>
						</span>`
				);
			} else if (content.type === 'channel') {
				return small_frame(html`
					<div>
						${content.subscribed ? 'subscribed to' : 'unsubscribed from'}
						<a href=${'#q=' + encodeURIComponent('#' + content.channel)}
							>#${content.channel}</a
						>
					</div>
				`);
			} else if (typeof this.message.content == 'string') {
				if (this.message?.decrypted) {
					if (this.format == 'decrypted') {
						return small_frame(
							html`<span>🔓</span>
								<pre>${JSON.stringify(this.message.decrypted, null, 2)}</pre>`
						);
					} else {
						return small_frame(
							html`<span>🔓</span>
								<div>${this.message.decrypted.type}</div>`
						);
					}
				} else {
					return small_frame(html`<span>🔒</span>`);
				}
			} else {
				return small_frame(html`<div><b>type</b>: ${content.type}</div>`);
			}
		} else {
			return small_frame(this.render_raw());
		}
	}
}

customElements.define('tf-message', TfMessageElement);