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 { ClientResponseError, type BobbinContext } from "./client";
import { jsonGet } from "./_request";
import { httpStatusFor } from "./load";
import { rkeyFromUri } from "./uri";
import type * as ShTangledFeedGetStar from "./lexicons/types/sh/tangled/feed/getStar";
import type * as ShTangledGraphFollow from "./lexicons/types/sh/tangled/graph/follow";
import type * as ShTangledGraphGetFollow from "./lexicons/types/sh/tangled/graph/getFollow";
import type * as ShTangledGraphVouch from "./lexicons/types/sh/tangled/graph/vouch";

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

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

// 404 means no follow exists, other errors propagate
export const getFollowRkey = async (
	ctx: BobbinContext,
	actor: string,
	subject: string
): Promise<string | null> => {
	try {
		const { uri } = await jsonGet<ShTangledGraphGetFollow.$output>(
			ctx,
			"sh.tangled.graph.getFollow",
			{ actor: actor as Did, subject: subject as Did } satisfies ShTangledGraphGetFollow.$params
		);
		return rkeyFromUri(uri);
	} catch (cause) {
		if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null;
		throw cause;
	}
};

// 404 means no star exists, other errors propagate
export const getStarRkey = async (
	ctx: BobbinContext,
	actor: string,
	subject: string
): Promise<string | null> => {
	try {
		const { uri } = await jsonGet<ShTangledFeedGetStar.$output>(ctx, "sh.tangled.feed.getStar", {
			actor: actor as Did,
			subject
		} satisfies ShTangledFeedGetStar.$params);
		return rkeyFromUri(uri);
	} catch (cause) {
		if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null;
		throw cause;
	}
};

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