import { ok } from '@atcute/client';
import { mainSchema as createRecordSchema } from '@atcute/atproto/types/repo/createRecord';
import { mainSchema as deleteRecordSchema } from '@atcute/atproto/types/repo/deleteRecord';
import type { Did, Nsid, RecordKey } from '@atcute/lexicons/syntax';
import type { OAuthUserAgent } from '@atcute/oauth-browser-client';
import { createClient } from '$lib/auth/agent';
import type { BobbinContext } from './client';
import { items } from './pagination';
import { rkeyFromUri } from './uri';
import type * as ShTangledGraphFollow from './lexicons/types/sh/tangled/graph/follow';
import type * as ShTangledGraphVouch from './lexicons/types/sh/tangled/graph/vouch';
import type * as ShTangledFeedStar from './lexicons/types/sh/tangled/feed/star';

export type FollowRecord = ShTangledGraphFollow.Main;
export type VouchRecord = ShTangledGraphVouch.Main;
export type StarRecord = ShTangledFeedStar.Main;

const FOLLOW_COLLECTION = 'sh.tangled.graph.follow' as Nsid;
const STAR_COLLECTION = 'sh.tangled.feed.star' as Nsid;

// TODO(bobbin): needs a relation point-lookup (e.g. graph.getFollow?actor=&subject=)
// or a `viewer` hydration param on lists; scanning listFollowsBy pages is O(follows).
export const findFollowRkey = async (
	ctx: BobbinContext,
	viewerDid: string,
	subject: string,
	options: { maxPages?: number } = {}
): Promise<string | null> => {
	for await (const item of items(
		ctx,
		'sh.tangled.graph.listFollowsBy',
		{ subject: viewerDid as Did },
		{ maxPages: options.maxPages ?? 10 }
	)) {
		if ((item.value as FollowRecord).subject === subject) return rkeyFromUri(item.uri);
	}
	return null;
};

const createGenericRecord = async <T extends { $type: string }>(
	agent: OAuthUserAgent,
	collection: Nsid,
	record: T
): Promise<string> => {
	const rpc = createClient(agent);
	const result = await ok(
		rpc.call(createRecordSchema, {
			input: { repo: agent.sub, collection, record }
		})
	);
	return rkeyFromUri(result.uri);
};

const deleteGenericRecord = async (
	agent: OAuthUserAgent,
	collection: Nsid,
	rkey: string
): Promise<void> => {
	const rpc = createClient(agent);
	await ok(
		rpc.call(deleteRecordSchema, {
			input: { repo: agent.sub, collection, rkey: rkey as RecordKey }
		})
	);
};

export const createFollow = (agent: OAuthUserAgent, subject: string): Promise<string> =>
	createGenericRecord(agent, FOLLOW_COLLECTION, {
		$type: 'sh.tangled.graph.follow',
		subject: subject as Did,
		createdAt: new Date().toISOString()
	});

export const deleteFollow = (agent: OAuthUserAgent, rkey: string): Promise<void> =>
	deleteGenericRecord(agent, FOLLOW_COLLECTION, rkey);

export const createStar = (agent: OAuthUserAgent, repoDid: string): Promise<string> =>
	createGenericRecord(agent, STAR_COLLECTION, {
		$type: 'sh.tangled.feed.star',
		subject: {
			$type: 'sh.tangled.feed.star#repo',
			did: repoDid as Did
		},
		createdAt: new Date().toISOString()
	});

export const deleteStar = (agent: OAuthUserAgent, rkey: string): Promise<void> =>
	deleteGenericRecord(agent, STAR_COLLECTION, rkey);

// TODO(bobbin): same relation-lookup gap as findFollowRkey; walking every star of
// the viewer to build this map is O(stars) per page load.
export const listStarRkeys = async (
	ctx: BobbinContext,
	viewerDid: string,
	options: { maxPages?: number } = {}
): Promise<Map<string, string>> => {
	const rkeys = new Map<string, string>();
	for await (const item of items(
		ctx,
		'sh.tangled.feed.listStarsBy',
		{ subject: viewerDid as Did },
		{ maxPages: options.maxPages ?? 10 }
	)) {
		const value = item.value as StarRecord;
		if (value.subject.$type === 'sh.tangled.feed.star#repo') {
			rkeys.set(value.subject.did, rkeyFromUri(item.uri));
		}
	}
	return rkeys;
};
