import type { ApObject } from "./type.js";
import { getApIds } from "./type.js";
import Resolver from "./resolver.js";
import { resolvePerson } from "./models/person.js";
import { unique, concat } from "@/prelude/array.js";
import promiseLimit from "promise-limit";
import type {
	CacheableRemoteUser,
	CacheableUser,
} from "@/models/entities/user.js";
import { User } from "@/models/entities/user.js";
import { RecursionLimiter } from "@/models/repositories/user-profile.js";

type Visibility = "public" | "home" | "followers" | "specified";

type AudienceInfo = {
	visibility: Visibility;
	mentionedUsers: CacheableUser[];
	visibleUsers: CacheableUser[];
};

export async function parseAudience(
	actor: CacheableRemoteUser,
	to?: ApObject,
	cc?: ApObject,
	resolver?: Resolver,
	limiter: RecursionLimiter = new RecursionLimiter()
): Promise<AudienceInfo> {
	const toGroups = groupingAudience(getApIds(to), actor);
	const ccGroups = groupingAudience(getApIds(cc), actor);

	const others = unique(concat([toGroups.other, ccGroups.other]));

	resolver ??= new Resolver();
	const limit = promiseLimit<CacheableUser | null>(2);
	const mentionedUsers = (
		await Promise.all(
			others.map((id) =>
				limit(() => resolvePerson(id, resolver, limiter).catch(() => null)),
			),
		)
	).filter((x): x is CacheableUser => x != null);

	if (toGroups.public.length > 0) {
		return {
			visibility: "public",
			mentionedUsers,
			visibleUsers: [],
		};
	}

	if (ccGroups.public.length > 0) {
		return {
			visibility: "home",
			mentionedUsers,
			visibleUsers: [],
		};
	}

	if (toGroups.followers.length > 0) {
		return {
			visibility: "followers",
			mentionedUsers,
			visibleUsers: [],
		};
	}

	return {
		visibility: "specified",
		mentionedUsers,
		visibleUsers: mentionedUsers,
	};
}

function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
	const groups = {
		public: [] as string[],
		followers: [] as string[],
		other: [] as string[],
	};

	for (const id of ids) {
		if (isPublic(id)) {
			groups.public.push(id);
		} else if (isFollowers(id, actor)) {
			groups.followers.push(id);
		} else {
			groups.other.push(id);
		}
	}

	groups.other = unique(groups.other);

	return groups;
}

function isPublic(id: string) {
	return [
		"https://www.w3.org/ns/activitystreams#Public",
		"as:Public",
		"Public",
	].includes(id);
}

function isFollowers(id: string, actor: CacheableRemoteUser) {
	return id === (actor.followersUri || `${actor.uri}/followers`);
}
