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

class TfTabNewsFeedElement extends LitElement {
	static get properties() {
		return {
			whoami: {type: String},
			users: {type: Object},
			hash: {type: String},
			following: {type: Array},
			messages: {type: Array},
			drafts: {type: Object},
			expanded: {type: Object},
			channels_unread: {type: Object},
			channels_latest: {type: Object},
			loading: {type: Number},
			time_range: {type: Array},
			time_loading: {type: Array},
		};
	}

	static styles = styles;

	constructor() {
		super();
		let self = this;
		this.whoami = null;
		this.users = {};
		this.hash = '#';
		this.following = [];
		this.drafts = {};
		this.expanded = {};
		this.channels_unread = {};
		this.channels_latest = {};
		this.start_time = new Date().valueOf();
		this.time_range = [0, 0];
		this.time_loading = undefined;
		this.loading = 0;
	}

	channel() {
		return this.hash.startsWith('##')
			? this.hash.substring(2)
			: this.hash.substring(1);
	}

	async fetch_messages(start_time, end_time) {
		this.time_loading = [start_time, end_time];
		let result;
		if (this.hash == '#@') {
			result = await tfrpc.rpc.query(
				`
					SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
					FROM messages_fts(?1)
					JOIN messages ON messages.rowid = messages_fts.rowid
					JOIN json_each(?2) AS following ON messages.author = following.value
					WHERE
						messages.author != ?1 AND
						messages.timestamp >= ?3 AND
						messages.timestamp < ?4
					ORDER BY timestamp DESC limit 20
				`,
				[
					'"' + this.whoami.replace('"', '""') + '"',
					JSON.stringify(this.following),
					start_time,
					end_time,
				]
			);
		} else if (this.hash.startsWith('#@')) {
			result = await tfrpc.rpc.query(
				`
					WITH mine AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
						FROM messages
						WHERE messages.author = ?
						ORDER BY sequence DESC)
					SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
						FROM mine
						JOIN messages_refs ON mine.id = messages_refs.ref
						JOIN messages ON messages_refs.message = messages.id
						WHERE
							mine.timestamp >= ?2 AND
							mine.timestamp < ?3
					UNION
					SELECT * FROM mine
					WHERE
						mine.timestamp >= ?2 AND
						mine.timestamp < ?3
				`,
				[this.hash.substring(1), start_time, end_time]
			);
		} else if (this.hash.startsWith('#%')) {
			result = await tfrpc.rpc.query(
				`
					SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
					FROM messages
					WHERE id = ?1
					UNION
					SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
					FROM messages JOIN messages_refs
					ON messages.id = messages_refs.message
					WHERE messages_refs.ref = ?1
				`,
				[this.hash.substring(1)]
			);
		} else if (this.hash.startsWith('##')) {
			let promises = [];
			const k_following_limit = 256;
			for (let i = 0; i < this.following.length; i += k_following_limit) {
				promises.push(
					tfrpc.rpc.query(
						`
						WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM messages
							JOIN json_each(?) AS following ON messages.author = following.value
							WHERE
								messages.timestamp >= ? AND
								messages.timestamp < ? AND
								messages.content ->> 'channel' = ?
							ORDER BY messages.timestamp DESC)
						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM news
							JOIN messages_refs ON news.id = messages_refs.ref
							JOIN messages ON messages_refs.message = messages.id
						UNION
						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM news
							JOIN messages_refs ON news.id = messages_refs.message
							JOIN messages ON messages_refs.ref = messages.id
						UNION
						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM messages_fts(?5)
							JOIN messages ON messages.rowid = messages_fts.rowid
							JOIN json_each(?1) AS following ON messages.author = following.value
							JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4
							WHERE
								messages.timestamp >= ?2 AND
								messages.timestamp < ?3
						UNION
						SELECT news.* FROM news
					`,
						[
							JSON.stringify(this.following.slice(i, i + k_following_limit)),
							start_time,
							end_time,
							this.hash.substring(2),
							'"#' + this.hash.substring(2).replace('"', '""') + '"',
						]
					)
				);
			}
			result = [].concat(...(await Promise.all(promises)));
		} else {
			let promises = [];
			const k_following_limit = 256;
			for (let i = 0; i < this.following.length; i += k_following_limit) {
				promises.push(
					tfrpc.rpc.query(
						`
						WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
						FROM messages
						JOIN json_each(?) AS following ON messages.author = following.value
						WHERE messages.timestamp >= ? AND messages.timestamp < ?
						ORDER BY messages.timestamp DESC)
						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM news
							JOIN messages_refs ON news.id = messages_refs.ref
							JOIN messages ON messages_refs.message = messages.id
						UNION
						SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
							FROM news
							JOIN messages_refs ON news.id = messages_refs.message
							JOIN messages ON messages_refs.ref = messages.id
						UNION
						SELECT news.* FROM news
					`,
						[
							JSON.stringify(this.following.slice(i, i + k_following_limit)),
							start_time,
							end_time,
						]
					)
				);
			}
			result = [].concat(...(await Promise.all(promises)));
		}
		this.time_loading = undefined;
		return result;
	}

	update_time_range_from_messages(messages) {
		this.time_range = [
			messages.reduce(
				(accumulator, current) => Math.min(accumulator, current.timestamp),
				this.time_range[0]
			),
			messages.reduce(
				(accumulator, current) => Math.max(accumulator, current.timestamp),
				this.time_range[1]
			),
		];
	}

	async load_more() {
		this.loading++;
		this.loading_canceled = false;
		try {
			let more = [];
			while (!more.length && !this.loading_canceled) {
				let last_start_time = this.start_time;
				this.start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
				more = await this.fetch_messages(this.start_time, last_start_time);
				this.update_time_range_from_messages(
					more.filter(
						(x) =>
							x.timestamp >= this.start_time && x.timestamp < last_start_time
					)
				);
			}
			this.messages = await this.decrypt([...more, ...this.messages]);
		} finally {
			this.loading--;
		}
	}

	cancel_load() {
		this.loading_canceled = true;
	}

	async decrypt(messages) {
		let result = [];
		for (let message of messages) {
			let content;
			try {
				content = JSON.parse(message?.content);
			} catch {}
			if (typeof content === 'string') {
				let decrypted;
				try {
					decrypted = await tfrpc.rpc.try_decrypt(this.whoami, content);
				} catch {}
				if (decrypted) {
					try {
						message.decrypted = JSON.parse(decrypted);
					} catch {
						message.decrypted = decrypted;
					}
				}
			}
			result.push(message);
		}
		return result;
	}

	async load_latest() {
		this.loading++;
		let now = new Date().valueOf();
		let end_time = now + 24 * 60 * 60 * 1000;
		let messages = [];
		try {
			messages = await this.fetch_messages(this.time_range[1], end_time);
			messages = await this.decrypt(messages);
			this.update_time_range_from_messages(
				messages.filter(
					(x) => x.timestamp >= this.time_range[1] && x.timestamp < end_time
				)
			);
		} finally {
			this.loading--;
		}
		this.messages = [...this.messages, ...messages];
		console.log('done loading latest messages.');
	}

	async load_messages() {
		let self = this;
		this.loading++;
		let messages = [];
		try {
			this.messages = [];
			this._messages_hash = this.hash;
			this._messages_following = this.following;
			let now = new Date().valueOf();
			let start_time = now - 24 * 60 * 60 * 1000;
			this.start_time = start_time;
			this.time_range = [this.start_time, now + 24 * 60 * 60 * 1000];
			messages = await this.fetch_messages(
				this.time_range[0],
				this.time_range[1]
			);
			this.update_time_range_from_messages(
				messages.filter(
					(x) =>
						x.timestamp >= this.time_range[0] &&
						x.timestamp < this.time_range[1]
				)
			);
			messages = await this.decrypt(messages);
			if (!messages.length) {
				let more = [];
				while (!more.length && start_time >= 0) {
					let last_start_time = start_time;
					start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
					more = await this.fetch_messages(start_time, last_start_time);
					this.update_time_range_from_messages(
						more.filter(
							(x) => x.timestamp >= start_time && x.timestamp < last_start_time
						)
					);
				}
				messages = await this.decrypt([...more, ...this.messages]);
			}
		} finally {
			this.loading--;
		}
		this.messages = messages;
		this.time_loading = undefined;
		console.log(`loading messages done for ${self.whoami}`);
	}

	mark_all_read() {
		let newest = this.messages.reduce(
			(accumulator, current) => Math.max(accumulator, current.rowid),
			this.channels_latest[this.channel()] ?? -1
		);
		if (newest >= 0) {
			this.dispatchEvent(
				new CustomEvent('channelsetunread', {
					bubbles: true,
					composed: true,
					detail: {
						channel: this.channel(),
						unread: newest + 1,
					},
				})
			);
		}
	}

	render() {
		if (
			!this.messages ||
			this._messages_hash !== this.hash ||
			this._messages_following !== this.following
		) {
			console.log(
				`loading messages for ${this.whoami} (following ${this.following.length})`
			);
			this.load_messages();
		}
		let more;
		if (!this.hash.startsWith('#%')) {
			more = html`
				<p>
					<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
						Mark All Read
					</button>
					<button
						?disabled=${this.loading}
						class="w3-button w3-theme-d1"
						@click=${this.load_more}
					>
						Load More
					</button>
					<button
						class=${'w3-button w3-theme-d1' + (this.loading ? '' : ' w3-hide')}
						@click=${this.cancel_load}
					>
						Cancel
					</button>
					<span
						>Showing
						${new Date(
							this.time_loading
								? Math.min(this.time_loading[0], this.time_range[0])
								: this.time_range[0]
						).toLocaleDateString()}
						-
						${new Date(
							this.time_loading
								? Math.max(this.time_loading[1], this.time_range[1])
								: this.time_range[1]
						).toLocaleDateString()}.</span
					>
				</p>
			`;
		}
		return html`
			<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
				Mark All Read
			</button>
			<tf-news
				id="news"
				whoami=${this.whoami}
				.users=${this.users}
				.messages=${this.messages}
				.following=${this.following}
				.drafts=${this.drafts}
				.expanded=${this.expanded}
				channel=${this.channel()}
				channel_unread=${this.channels_unread?.[this.channel()]}
			></tf-news>
			${more}
		`;
	}
}

customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);