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;
};
