import {
	LitElement,
	css,
	html,
	map,
	repeat,
	render,
	unsafeCSS,
	unsafeHTML,
	until,
} 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, generate_theme} 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},
			channel: {type: String},
			channel_unread: {type: Number},
			recent_reactions: {type: Array},
			depth: {type: Number},
			hash: {type: String},
			revision: {type: Number},
		};
	}

	static styles = styles;

	constructor() {
		super();
		let self = this;
		this.whoami = null;
		this.message = {};
		this.users = {};
		this.drafts = {};
		this.format = 'message';
		this.expanded = {};
		this.channel_unread = -1;
		this.recent_reactions = [];
		this.depth = 0;
	}

	connectedCallback() {
		super.connectedCallback();
		this._click_callback = this.document_click.bind(this);
		this._blob_stored = this.blob_stored.bind(this);
		document.body.addEventListener('mouseup', this._click_callback);
		window.addEventListener('blob-stored', this._blob_stored);
	}

	disconnectedCallback() {
		super.disconnectedCallback();
		window.removeEventListener('blob-stored', this._blob_stored);
		document.body.removeEventListener('mouseup', this._click_callback);
	}

	document_click(event) {
		let content = this.renderRoot.querySelector('.w3-dropdown-content');
		let target = event.target;
		if (content && !content.contains(target)) {
			content.classList.remove('w3-show');
		}
	}

	blob_stored(event) {
		for (let img of this.shadowRoot.querySelectorAll('img')) {
			if (
				img.src.split('?')[0].endsWith('/view') &&
				img.complete &&
				!img.naturalWidth
			) {
				let src = img.src.split('?')[0];
				let query = (img.src.split('?')[1] ?? '')
					.split('&')
					.filter((x) => x !== '' && !x.startsWith('tfreload='));
				query.push(`tfreload=${new Date().valueOf()}`);
				img.src = `${src}?${query.join('&')}`;
			}
		}
	}

	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 === 'Unlike' ||
				expression === 'unlike' ||
				expression == 'undig'
			) {
				return '👎';
			} else if (expression === 'heart') {
				return '❤️';
			} else if (
				(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
			) {
				return '👍';
			} else {
				return expression;
			}
		}
		if (this.message?.votes?.length) {
			return html` <footer class="w3-container">
				<div
					class="w3-button w3-bar"
					style="padding: 0"
					@click=${this.show_reactions}
				>
					${(this.message.votes || []).map(
						(vote) => html`
							<span
								class="w3-bar-item w3-padding-small"
								title="${this.users[vote.author]?.name ??
								vote.author} ${new Date(vote.timestamp)}"
							>
								${normalize_expression(vote.content.vote.expression)}
							</span>
						`
					)}
				</div>
			</footer>`;
		}
	}

	render_json(value) {
		let json = JSON.stringify(value, null, 2);
		return html`
			<pre style="white-space: pre-wrap; overflow-wrap: anywhere">${json}</pre>
		`;
	}

	render_raw() {
		let content = this.get_content();
		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: content,
			signature: this.message?.signature,
		};
		return this.render_json(raw);
	}

	vote(emoji) {
		let reaction = emoji;
		let message = this.message.id;
		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,
			this.recent_reactions
		);
	}

	flag(event) {
		let reason = prompt(
			'What is the reason for reporting this content (spam, nsfw, ...)?',
			'offensive'
		);
		if (reason !== undefined) {
			tfrpc.rpc
				.appendMessage(this.whoami, {
					type: 'flag',
					flag: {
						link: this.message.id,
						reason: reason.length ? reason : undefined,
					},
				})
				.catch(function (error) {
					alert(error?.message);
				});
		}
	}

	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 = '100vw';
		img.style.maxHeight = '100vh';
		img.style.display = 'block';
		img.style.margin = 'auto';
		img.style.objectFit = 'contain';
		img.style.width = '100vw';
		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 != 'none') {
				next.style.display = 'none';
			} else {
				next.style.display = 'block';
			}
		}
	}

	render_mention(mention) {
		if (!mention?.link || typeof mention.link != 'string') {
			return this.render_json(mention);
		} 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=${'#' + 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 this.render_json(mention);
		}
	}

	render_mentions() {
		let content = this.get_content();
		let mentions = content?.mentions || [];
		mentions = mentions.filter(
			(x) => content?.text?.indexOf(typeof x === 'string' ? x : 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;
	}

	expanded_key() {
		return (
			this.message?.id || this.message?.messages?.map((x) => x.id).join(':')
		);
	}

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

	toggle_expanded(tag) {
		let key = this.expanded_key();
		this.set_expanded(!this.expanded[key + (tag || '')], tag);
	}

	is_expanded(tag) {
		let key = this.expanded_key();
		return this.expanded[key + (tag || '')];
	}

	render_children() {
		let self = this;
		if (this.message.child_messages?.length) {
			if (!this.expanded[this.expanded_key()]) {
				return html`
					<button
						class="w3-button w3-theme-d1 w3-block w3-bar"
						style="box-sizing: border-box"
						@click=${() => self.set_expanded(true)}
					>
						+ ${this.total_child_messages(this.message) + ' More'}
					</button>
				`;
			} else {
				return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4">
					${repeat(
						this.message.child_messages || [],
						(x) => x.id,
						(x) =>
							html`<li style="padding: 0">
								<tf-message
									.message=${x}
									whoami=${this.whoami}
									.users=${this.users}
									.drafts=${this.drafts}
									.expanded=${this.expanded}
									channel=${this.channel}
									channel_unread=${this.channel_unread}
									.recent_reactions=${this.recent_reactions}
									depth=${this.depth + 1}
									hash=${this.hash}
								></tf-message>
							</li>`
					)}
					<li style="padding: 0" class="w3-margin-bottom">
						<button
							class="w3-button w3-theme-d1 w3-block w3-bar"
							style="box-sizing: border-box"
							@click=${() => self.set_expanded(false)}
						>
							Collapse
						</button>
					</li>
				</ul>`;
			}
		} else {
			return undefined;
		}
	}

	mark_unread() {
		this.dispatchEvent(
			new CustomEvent('channelsetunread', {
				bubbles: true,
				composed: true,
				detail: {
					channel: this.channel,
					unread: this.message.rowid,
				},
			})
		);
	}

	render_channels() {
		let content = this.get_content();
		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>`);
	}

	class_background() {
		return (
			(this.hash == '#' + this.message?.id ? 'w3-leftbar ' : '') +
			(this.message?.decrypted
				? 'w3-pale-red'
				: this.allow_unread() && this.message?.rowid >= this.channel_unread
					? 'w3-theme-d2'
					: 'w3-theme-d4')
		);
	}

	get_content() {
		let content = this.message?.content;
		if (this.message?.decrypted?.type == 'post') {
			content = this.message.decrypted;
		}
		if (this.message.revisions) {
			content = Object.assign({}, content);
			let stop =
				this.revision === undefined
					? (this.message?.revisions?.length ?? 0)
					: this.revision;
			for (let revision of this.message.revisions.slice(0, stop)) {
				for (let key of ['text', 'mentions', 'channel', 'contentWarning']) {
					if (revision.content?.[key]) {
						content[key] = revision.content[key];
					}
				}
			}
		}
		return content;
	}

	copy_id(event) {
		navigator.clipboard.writeText(this.message?.id).catch(function (e) {
			console.log(e);
		});
	}

	toggle_menu(event) {
		event.srcElement.parentNode
			.querySelector('.w3-dropdown-content')
			.classList.toggle('w3-show');
	}

	show_edit() {
		let content = this.get_content();
		let revisionBranch = this.message?.id;
		if (this.message?.revisions) {
			revisionBranch =
				this.message.revisions[this.message.revisions.length - 1].id;
		}
		let event = new CustomEvent('tf-draft', {
			bubbles: true,
			composed: true,
			detail: {
				id: this.message?.id,
				draft: {
					revisionRoot: this.message?.id,
					revisionBranch: revisionBranch,
					encrypt_to: this.message?.decrypted?.recps,
					text: content?.text,
					mentions: [...(content.mentions ?? [])],
				},
			},
		});
		this.dispatchEvent(event);
	}

	render_menu() {
		let content = this.get_content();
		let formats = [['message', 'Message']];
		if (content?.type == 'post' || content?.type == 'blog') {
			formats.push(['md', 'Markdown']);
		}
		if (this.message?.decrypted) {
			formats.push(['decrypted', 'Decrypted']);
		}
		formats.push(['raw', 'Raw']);
		return html`
			<div class="w3-bar-item w3-right">
				<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
					%
				</button>
				<div
					class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
					style="right: 48px"
				>
					<a
						target="_top"
						class="w3-button w3-bar-item"
						href=${'#' + encodeURIComponent(this.message?.id)}
						>View Message</a
					>
					<button
						class="w3-button w3-bar-item w3-border-bottom"
						@click=${this.copy_id}
					>
						Copy ID
					</button>
					${this.drafts[this.message?.id] === undefined
						? html`
								<button class="w3-button w3-bar-item" @click=${this.show_reply}>
									↩️ Reply
								</button>
							`
						: undefined}
					<button class="w3-button w3-bar-item" @click=${this.react}>
						👍 React
					</button>
					<button
						id="button_flag"
						class="w3-button w3-bar-item w3-border-bottom"
						@click=${this.flag}
					>
						🚩 Flag
					</button>
					${formats.map(
						([format, name]) => html`
							<button
								class="w3-button w3-bar-item"
								style=${format == this.format ? 'font-weight: bold' : ''}
								@click=${() => (this.format = format)}
							>
								${name}
							</button>
						`
					)}
					${this.whoami == this.message?.author && content?.type === 'post'
						? html`
								<button
									class="w3-button w3-bar-item w3-border-top"
									@click=${this.show_edit}
								>
									Edit
								</button>
							`
						: undefined}
				</div>
			</div>
		`;
	}

	history_go(delta) {
		this.revision = Math.min(
			Math.max((this.revision ?? this.message.revisions.length) + delta, 0),
			this.message.revisions.length
		);
		if (this.revision === this.message.revisions.length) {
			this.revision = undefined;
		}
	}

	render_revision_history() {
		if (this.message?.revisions) {
			return html`
				<div class="w3-bar-item w3-right w3-opacity" style="text-wrap: nowrap">
					<button
						class="w3-btn w3-theme-d1"
						@click=${() => this.history_go(-1)}
						?disabled=${this.revision === 0}
					>
						◄
					</button>
					<button
						class="w3-btn w3-theme-d1"
						@click=${() => this.history_go(1)}
						?disabled=${this.revision === undefined}
					>
						►
					</button>
					(${(this.revision ?? this.message.revisions.length) + 1}/${this
						.message.revisions.length + 1}
					edited
					${new Date(
						this.message.revisions[
							this.revision ?? this.message.revisions.length - 1
						].timestamp
					).toLocaleString()})
				</div>
			`;
		}
	}

	render_header() {
		let is_encrypted = this.message?.decrypted
			? html`<span class="w3-bar-item">🔓</span>`
			: typeof this.message?.content == 'string'
				? html`<span class="w3-bar-item">🔒</span>`
				: undefined;
		return html`
			<header class="w3-bar">
				<span class="w3-bar-item">
					${this.render_unread_icon()}<tf-user
						id=${this.message.author}
						.users=${this.users}
					></tf-user>
				</span>
				${is_encrypted} ${this.render_menu()}
				<div class="w3-bar-item w3-right" style="text-wrap: nowrap">
					${new Date(this.message.timestamp).toLocaleString()}
				</div>
				${this.render_revision_history()}
			</header>
		`;
	}

	render_frame(inner) {
		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-border-theme ${this.class_background()} ${this
					.depth == 0
					? 'w3-margin-top'
					: ''}"
				style="overflow-wrap: anywhere; display: block; max-width: 100%"
			>
				${inner}
			</div>
		`;
	}

	render_refs() {
		if (this.message?.refs) {
			let self = this;
			return html`<div class="w3-container">
				<h3>Referring messages</h3>
				${map(
					this.message.refs,
					(ref) => html`
						<tf-message
							.message=${ref}
							whoami=${self.whoami}
							.users=${self.users}
							.drafts=${self.drafts}
							.expanded=${self.expanded}
							channel=${self.channel}
							channel_unread=${self.channel_unread}
							.recent_reactions=${self.recent_reactions}
							depth=${self.depth + 1}
							hash=${this.hash}
						></tf-message>
					`
				)}
			</div>`;
		}
	}

	render_small_frame(inner) {
		let self = this;
		return this.render_frame(html`
			${self.render_header()}
			<div class="w3-container">
				${self.format == 'raw'
					? html`${self.render_raw()}`
					: self.render_flagged(inner)}
			</div>
			${self.render_votes()} ${self.render_refs()}
			${(self.message.child_messages || []).map(
				(x) => html`
					<tf-message
						.message=${x}
						whoami=${self.whoami}
						.users=${self.users}
						.drafts=${self.drafts}
						.expanded=${self.expanded}
						channel=${self.channel}
						channel_unread=${self.channel_unread}
						.recent_reactions=${self.recent_reactions}
						depth=${self.depth + 1}
						hash=${this.hash}
					></tf-message>
				`
			)}
		`);
	}

	render_actions() {
		let content = this.get_content();
		let reply =
			this.drafts[this.message?.id] !== undefined
				? html`
						<div class="w3-section w3-container">
							<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}
								.recent_reactions=${this.recent_reactions}
							></tf-compose>
						</div>
					`
				: undefined;
		return html`
			${reply}
			<footer>${this.render_children()}</footer>
		`;
	}

	contact_description(content) {
		return content.following && content.blocking
			? 'following and blocking'
			: content.following
				? 'following'
				: content.blocking
					? 'blocking'
					: content.blocking !== undefined
						? 'no longer blocking'
						: content.following !== undefined
							? 'no longer following'
							: '';
	}

	content_group_by_author() {
		let sorted = this.message.messages
			.map((x) => [
				x.author,
				this.contact_description(x.content),
				x.content.contact,
				x,
			])
			.sort();
		let result = [];
		let last;
		let group;
		for (let row of sorted) {
			if (last && last[0] == row[0] && last[1] == row[1]) {
				group.push(row[2]);
			} else {
				if (group) {
					result.push({author: last[0], action: last[1], users: group});
				}
				last = row;
				group = [row[2]];
			}
		}
		if (group) {
			result.push({author: last[0], action: last[1], users: group});
		}
		return result;
	}

	channel_group_by_author() {
		let sorted = this.message.messages
			.map((x) => [
				x.author,
				x.content.subscribed ? 'subscribed to' : 'unsubscribed from',
				x.content.channel,
				x,
			])
			.sort();
		let result = [];
		let last;
		let group;
		for (let row of sorted) {
			if (last && last[0] == row[0] && last[1] == row[1]) {
				group.push(row[2]);
			} else {
				if (group) {
					result.push({author: last[0], action: last[1], channels: group});
				}
				last = row;
				group = [row[2]];
			}
		}
		if (group) {
			result.push({author: last[0], action: last[1], channels: group});
		}
		return result;
	}

	allow_unread() {
		return (
			this.channel == '@' ||
			(!this.channel.startsWith('@') && !this.channel.startsWith('%'))
		);
	}

	render_unread_icon() {
		return this.allow_unread() && this.message?.rowid >= this.channel_unread
			? html`✉️`
			: undefined;
	}

	render_flagged(inner) {
		if (this.message.flags) {
			return html`
				<div
					class="w3-panel w3-round-xlarge w3-theme-l4 w3"
					style="cursor: pointer"
					@click=${(x) => this.toggle_expanded(':cw')}
				>
					<p>
						${this.message.flags
							? html`<p>
									Caution: This message has been flagged
									${this.message.flags.length}
									time${this.message.flags.length == 1 ? '' : 's'}.
								</p>`
							: undefined}
					</p>
					<p class="w3-small">
						${inner !== undefined
							? this.is_expanded(':cw')
								? 'Show less'
								: 'Show more'
							: undefined}
					</p>
				</div>
				${this.is_expanded(':cw') ? inner : undefined}
			`;
		} else {
			return inner;
		}
	}

	_render() {
		let content = this.get_content();
		let class_background = this.class_background();
		let self = this;
		if (this.message?.type === 'contact_group') {
			if (this.expanded[this.expanded_key()]) {
				return this.render_frame(html`
					<div class="w3-padding">
						${this.message.messages.map(
							(x) =>
								html`<tf-message
									.message=${x}
									whoami=${this.whoami}
									.users=${this.users}
									.drafts=${this.drafts}
									.expanded=${this.expanded}
									channel=${this.channel}
									channel_unread=${this.channel_unread}
									depth=${this.depth + 1}
									hash=${this.hash}
								></tf-message>`
						)}
					</div>
					<button
						class="w3-button w3-theme-d1 w3-block w3-bar"
						style="box-sizing: border-box"
						@click=${() => self.set_expanded(false)}
					>
						Collapse
					</button>
				`);
			} else {
				return this.render_frame(html`
					<div class="w3-padding">
						${this.content_group_by_author().map(
							(x) => html`
								<div>
									<tf-user id=${x.author} .users=${this.users}></tf-user>
									${x.action}
									${x.users.map(
										(y) => html`
											<tf-user
												id=${y}
												.users=${this.users}
												icon_only="true"
											></tf-user>
										`
									)}
								</div>
							`
						)}
					</div>
					<button
						class="w3-button w3-theme-d1 w3-block w3-bar"
						style="box-sizing: border-box"
						@click=${() => self.set_expanded(true)}
					>
						Expand
					</button>
				`);
			}
		} else if (this.message?.type === 'channel_group') {
			if (this.expanded[this.expanded_key()]) {
				return this.render_frame(html`
					<div class="w3-padding">
						${this.message.messages.map(
							(x) =>
								html`<tf-message
									.message=${x}
									whoami=${this.whoami}
									.users=${this.users}
									.drafts=${this.drafts}
									.expanded=${this.expanded}
									channel=${this.channel}
									channel_unread=${this.channel_unread}
									depth=${this.depth + 1}
									hash=${this.hash}
								></tf-message>`
						)}
					</div>
					<button
						class="w3-button w3-theme-d1 w3-block w3-bar"
						style="box-sizing: border-box"
						@click=${() => self.set_expanded(false)}
					>
						Collapse
					</button>
				`);
			} else {
				return this.render_frame(html`
					<div class="w3-padding">
						${this.channel_group_by_author().map(
							(x) => html`
								<div>
									<tf-user id=${x.author} .users=${this.users}></tf-user>
									${x.action}
									${x.channels.map(
										(y) => html` <tf-tag tag=${'#' + y}></tf-tag> `
									)}
								</div>
							`
						)}
					</div>
					<button
						class="w3-button w3-theme-d1 w3-block w3-bar"
						style="box-sizing: border-box"
						@click=${() => self.set_expanded(true)}
					>
						Expand
					</button>
				`);
			}
		} else if (this.message.placeholder) {
			return this.render_frame(
				html`<div>
					<div class="w3-bar">
						<a
							class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button"
							target="_top"
							href=${'#' + encodeURIComponent(this.message?.id)}
						>
							This message is not currently available.
						</a>
						<div class="w3-bar-item w3-right">
							<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
								%
							</button>
							<div
								class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
								style="right: 48px"
							>
								<a
									target="_top"
									class="w3-button w3-bar-item"
									href=${'#' + encodeURIComponent(this.message?.id)}
									>View Message</a
								>
								<button
									class="w3-button w3-bar-item w3-border-bottom"
									@click=${this.copy_id}
								>
									Copy ID
								</button>
							</div>
						</div>
					</div>
					<div class="w3-container">${this.render_flagged(undefined)}</div>
					<div>${this.render_votes()}</div>
					${this.render_refs()}
					${(this.message.child_messages || []).map(
						(x) => html`
							<tf-message
								.message=${x}
								whoami=${this.whoami}
								.users=${this.users}
								.drafts=${this.drafts}
								.expanded=${this.expanded}
								channel=${this.channel}
								channel_unread=${this.channel_unread}
								depth=${this.depth + 1}
								hash=${this.hash}
							></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 @click=${this.body_click}><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 this.render_small_frame(html`
					<div class="w3-container">
						<p>${update} ${name} ${image} ${description}</p>
					</div>
				`);
			} else if (content.type == 'contact') {
				switch (this.format) {
					case 'message':
					default:
						return this.render_frame(html`
							<div class="w3-bar">
								<div class="w3-bar-item">
									<tf-user
										id=${this.message.author}
										.users=${this.users}
									></tf-user>
									is ${this.contact_description(content)}
									<tf-user id=${content.contact} .users=${this.users}></tf-user>
								</div>
								${this.render_menu()}
								<span class="w3-bar-item w3-right">
									${new Date(this.message.timestamp).toLocaleString()}
								</span>
							</div>
							${this.render_votes()} ${this.render_refs()}
							${this.render_actions()}
						`);
						break;
					case 'raw':
						return this.render_frame(html`
							${this.render_header()}
							<div class="w3-container">${this.render_raw()}</div>
							${this.render_votes()}
							${this.render_refs()}
							${this.render_actions()}
						</div>
						`);
						break;
				}
			} else if (content.type == 'post') {
				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 = this.render_json(content);
						break;
				}
				let content_warning = html`
					<div
						class="w3-panel w3-round-xlarge w3-theme-l4 w3"
						style="cursor: pointer"
						@click=${(x) => this.toggle_expanded(':cw')}
					>
						<p>
							${this.message.flags
								? html`<p>
										Caution: This message has been flagged
										${this.message.flags.length}
										time${this.message.flags.length == 1 ? '' : 's'}.
									</p>`
								: undefined}
							${content.contentWarning
								? html`<p>${content.contentWarning}</p>`
								: undefined}
						</p>

						<p class="w3-small">
							${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
						</p>
					</div>
				`;
				let content_html = html`
					${this.render_channels()}
					<div @click=${this.body_click}>${body}</div>
					${this.render_mentions()}
				`;
				let payload =
					this.message.flags || content.contentWarning
						? self.expanded[(this.message.id || '') + ':cw']
							? html` ${content_warning} ${content_html} `
							: content_warning
						: content_html;
				return this.render_frame(html`
					${this.render_header()}
					<div class="w3-container">${payload}</div>
					${this.render_votes()}
					${this.render_refs()}
					${this.render_actions()}
				</div>
				`);
			} else if (content.type === 'issue') {
				let body;
				switch (this.format) {
					case 'raw':
						body = this.render_raw();
						break;
					case 'message':
						body = content.text;
						break;
				}
				return this.render_frame(html`
					${this.render_header()}
					<div class="w3-container">${body}</div>
					${this.render_mentions()} ${this.render_votes()} ${this.render_refs()}
					${this.render_actions()}
				`);
			} else if (content.type === 'blog') {
				let self = this;
				self.blog_data = tfrpc.rpc.get_blob(content.blog).then(function (data) {
					return data
						? unsafeHTML(tfutils.markdown(data))
						: html`Blog post content unavailable.`;
				});
				let payload = this.expanded[(this.message.id || '') + ':blog']
					? until(this.blog_data, 'Loading...')
					: undefined;
				let body;
				switch (this.format) {
					case 'raw':
						body = this.render_raw();
						break;
					case 'md':
						body = content.summary;
						break;
					case 'message':
						body = html`
							<div
								class="w3-border w3-theme-l4 w3-round-xlarge"
								style="padding: 8px; margin: 4px; cursor: pointer"
								@click=${(x) => self.toggle_expanded(':blog')}
							>
								<h2>${content.title}</h2>
								<div style="display: flex; flex-direction: row; gap: 8px">
									${content.thumbnail
										? html`<img src=/${content.thumbnail}/view style="max-width: 25vw; max-height: 25vw"></img>`
										: undefined}
									<span>${content.summary}</span>
								</div>
								<p class="w3-small">
									${this.expanded[(this.message.id || '') + ':blog']
										? 'Show less'
										: 'Show more'}
								</p>
							</div>
							<div class="w3-container">${payload}</div>
						`;
						break;
				}
				return this.render_frame(html`
					${this.render_header()}
					<div>${body}</div>
					${this.render_mentions()} ${this.render_votes()} ${this.render_refs()}
					${this.render_actions()}
				`);
			} else if (content.type === 'pub') {
				return this.render_small_frame(
					html` <style>
							span {
								overflow-wrap: anywhere;
							}
						</style>
						<div class="w3-padding">
							<div>
								🍻
								<tf-user
									.users=${this.users}
									id=${content.address.key}
								></tf-user>
							</div>
							<pre>${content.address.host}:${content.address.port}</pre>
						</div>`
				);
			} else if (content.type === 'channel') {
				return this.render_small_frame(html`
					<div class="w3-container">
						<p>
							${content.subscribed ? 'subscribed to' : 'unsubscribed from'}
							<a href=${'#' + encodeURIComponent('#' + content.channel)}
								>#${content.channel}</a
							>
						</p>
					</div>
				`);
			} else if (content.type === 'bookclub') {
				return this.render_small_frame(html`
					<div>
						<h3>
							<a
								href=${'/~core/bookclub/#' +
								encodeURIComponent(this.message?.id)}
								>📖 ${content.title}</a
							>
						</h3>
						${(content.images ?? (content.image ? [content.image] : [])).map(
							(image) => html`
								<img
									class="w3-left w3-margin-right w3-margin-bottom"
									src=${'/' + image.link + '/view'}
									style="max-width: 128px; max-height: 128px"
									title=${image.name}
									@click=${() => this.show_image('/' + image.link + '/view')}
								/>
							`
						)}
						<p>
							${content?.author ??
							content.authors?.join?.(', ') ??
							content.authors}
						</p>
						<p>${content.description}></p>
					</div>
				`);
			} else if (
				content.type === 'tildefriends-app' ||
				content.type === 'tildefriends-app-edit'
			) {
				return this.render_small_frame(html`
					<div class="w3-container">
						<p class="w3-ul">
							<li>
								${content.type == 'tildefriends-app'
									? 'Tilde Friends App'
									: 'Tilde Friends App Edit'}:
								<b
									><a
										href="/${encodeURIComponent(
											content.updates ?? this.message.id
										)}/"
										>${content.name}</a
									></b
								>
							</li>
							${content.description
								? html`<li><b>Description</b>: ${content.description}</li>`
								: undefined}
							${content.change
								? html`<li>
										<b><a href="/${content.app}/">Changes</a></b
										>: ${content.change}
									</li>`
								: undefined}
						</p>
					</div>
				`);
			} else if (typeof this.message.content == 'string') {
				if (this.message?.decrypted) {
					if (this.format == 'decrypted') {
						return this.render_small_frame(
							html`<span class="w3-container">🔓</span> ${this.render_json(
									this.message.decrypted
								)}`
						);
					} else {
						return this.render_small_frame(
							html`<span class="w3-container">🔓</span>
								<div class="w3-container">${this.message.decrypted.type}</div>`
						);
					}
				} else {
					return this.render_small_frame();
				}
			} else {
				return this.render_small_frame(
					html`<p><b>type</b>: ${content.type}</p>`
				);
			}
		} else if (typeof this.message.content == 'string') {
			if (this.message?.decrypted) {
				if (this.format == 'decrypted') {
					return this.render_small_frame(
						this.render_json(this.message.decrypted)
					);
				} else {
					return this.render_small_frame(this.message.decrypted.type);
				}
			} else {
				return this.render_small_frame();
			}
		} else {
			return this.render_small_frame(this.render_raw());
		}
	}

	render() {
		return html`
			<style>
				${generate_theme()}
			</style>
			${this._render()}
		`;
	}
}

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