import {
	configureOAuth,
	createAuthorizationUrl,
	finalizeAuthorization,
	OAuthUserAgent,
	getSession,
	deleteStoredSession
} from '@atcute/oauth-browser-client';

import {
	CompositeDidDocumentResolver,
	LocalActorResolver,
	PlcDidDocumentResolver,
	WebDidDocumentResolver,
	XrpcHandleResolver
} from '@atcute/identity-resolver';
import type { ActorIdentifier } from '@atcute/lexicons';
import { err, ok, type Result } from '$lib/result';
import type { AtprotoDid } from '@atcute/lexicons/syntax';
import { clientId, oauthMetadata, redirectUri } from '$lib/oauth';
import { slingshotUrl } from '.';

configureOAuth({
	metadata: {
		client_id: clientId,
		redirect_uri: redirectUri
	},
	identityResolver: new LocalActorResolver({
		handleResolver: new XrpcHandleResolver({ serviceUrl: slingshotUrl.href }),

		didDocumentResolver: new CompositeDidDocumentResolver({
			methods: {
				plc: new PlcDidDocumentResolver(),
				web: new WebDidDocumentResolver()
			}
		})
	})
});

export const sessions = {
	get: async (did: AtprotoDid) => {
		const session = await getSession(did, { allowStale: true });
		return new OAuthUserAgent(session);
	},
	remove: async (did: AtprotoDid) => {
		try {
			const agent = await sessions.get(did);
			await agent.signOut();
		} catch {
			deleteStoredSession(did);
		}
	}
};

export const flow = {
	start: async (identifier: ActorIdentifier): Promise<Result<null, string>> => {
		try {
			const authUrl = await createAuthorizationUrl({
				target: { type: 'account', identifier },
				scope: oauthMetadata.scope
			});
			// recommended to wait for the browser to persist local storage before proceeding
			await new Promise((resolve) => setTimeout(resolve, 200));
			// redirect the user to sign in and authorize the app
			window.location.assign(authUrl);
			// if this is on an async function, ideally the function should never ever resolve.
			// the only way it should resolve at this point is if the user aborted the authorization
			// by returning back to this page (thanks to back-forward page caching)
			await new Promise((_resolve, reject) => {
				const listener = () => {
					reject(new Error(`user aborted the login request`));
				};
				window.addEventListener('pageshow', listener, { once: true });
			});
			return ok(null);
		} catch (error) {
			return err(`login error: ${error}`);
		}
	},
	finalize: async (url: URL): Promise<Result<OAuthUserAgent | null, string>> => {
		try {
			// createAuthorizationUrl asks server to put the params in the hash
			const params = new URLSearchParams(url.hash.slice(1));
			if (!params.has('code')) return ok(null);
			const { session } = await finalizeAuthorization(params);
			return ok(new OAuthUserAgent(session));
		} catch (error) {
			return err(`login error: ${error}`);
		}
	}
};
