import Path from "path"
import { createSource, createSourceOptions, downloadImages, fetchChannel, getPostIdFromPathname, isUnset } from "../lib.js"

class BlueskyError extends Error {}

let bluesky = {}

bluesky.createEndpoint = (domain, method) => {
	let endpoint = new URL('https://' + domain)
	endpoint.pathname = Path.join('xrpc', method)

	return endpoint
}

bluesky.createHeaders = (client) => {
	return {
		'Accept': 'application/json',
		'Authorization': 'Bearer ' + client.accessJwt
	}
}

bluesky.handleRequest = response => {
	return response
		.then(response => {
			response = response.json()

			if(response.error) {
				throw new BlueskyError(`${response.error}: ${response.message}`)
			}

			return response
		})
		.catch(err => {
			throw new BlueskyError(err)
		})
}

bluesky.login = async (handle, password, domain = 'bsky.social') => {
	let body = { identifier: handle, password }
	let endpoint = bluesky.createEndpoint(domain, 'com.atproto.server.createSession')

	let { accessJwt, refreshJwt } = await bluesky.handleRequest(
		fetch(endpoint.href, {
			method: 'POST',
			headers: {
				"Content-Type": "application/json"
			},
			body: JSON.stringify(body)
		})
	)

	return {
		domain,
		accessJwt,
		refreshJwt
	}
}

const getPostUriChunks = channel => {
	let guids = channel.querySelectorAll('guid')
	let storedUris = new Set()
	let chunks = []
	let uris = []
	let index = 0

	for(let guid of guids) {
		let uri = guid?.textContent
		
		if(!uri || storedUris.has(uri))
			continue

		if(index !== 0 && index % 25 === 0) {
			chunks.push(uris)
			uris = []
		}

		uris.push(uri)
		storedUris.add(uri)
		index++
	}

	chunks.push(uris)
	return chunks
}

bluesky.fetchAnnotations = async (client, channel) => {
	let chunks = getPostUriChunks(channel)
	let annotations = new Map()

	for(let uris of chunks) {
		let endpoint = bluesky.createEndpoint(client.domain, 'app.bsky.feed.getPosts')
		for(let uri of uris)
			endpoint.searchParams.append('uris', uri)

		let { posts } = await bluesky.handleRequest(
			fetch(endpoint.href, {
				headers: bluesky.createHeaders(client)
			})
		)
		
		for(let post of posts) {
			annotations.set(post.uri, post)
		}
	}

	return annotations
}

export default bluesky



bluesky.createSource = (handle, options = {}, client, postReducerCallback, cache) => {
	let startOfServerHostname = handle.indexOf('.')
	let user = handle.slice(0, startOfServerHostname)
	let serverHostname = handle.slice(startOfServerHostname + 1)
	  
	let source = {
		type: 'bluesky',
		description: `Aggregate feed for @${user} on bluesky`,
		hostname: 'bsky.app',
		pathname: 'profile/' + handle + "/rss",
		name: `${serverHostname}-${user}`,
		displayName: handle,
		user,
		handle,
		postsToPopulate: new Map(),
		postsChecked: new Set(),
		...createSourceOptions(options)
	}

	return createSource(
		source,
		isUnset(client) ? fetchChannel : bluesky.fetchAnnotatedChannelFor(client),
		bluesky.processPostAfterCallback(postReducerCallback),
		cache
	)
}

bluesky.fetchAnnotatedChannelFor = (client) => async (source) => {
	let channel = await fetchChannel(source)
	source.postAnnotations = await bluesky.fetchAnnotations(client, channel)

	return channel
}

bluesky.processPostAfterCallback = (postReducerCallback) => async (post) => {
	post.title = post.item.querySelector('description')?.textContent
	post = await postReducerCallback(post)

	return post
}

bluesky.isRepost = (post) => {
	let annotation = post.source.postAnnotations.get(post.guid)  
	let beenChecked = post.source.postsChecked.has(post.guid)

	post.source.postsChecked.add(post.guid)
	return annotation ?
		beenChecked && annotation.author.handle === post.source.handle :
		beenChecked
}

bluesky.pullImages = async (post, view, discardPostIfNoImages) => {
	let annotation = post.source.postAnnotations.get(post.guid)
	let noImages = isUnset(annotation.embed)
		|| annotation.embed.type === 'app.bsky.embed.images#view'

	if(isUnset(annotation) || noImages) {
		return discardPostIfNoImages ? null : post
	}

	post.images = await downloadImages(
		annotation.embed.images.map(image => image.fullsize),
		post.source,
		getPostIdFromPathname(post),
		view
	)
	return post
}