import { useQueryClient } from '../lib/atproto-query';
import { useAuth } from './auth';
import {
	getRepo,
	getRepoBranches,
	getRepoDefaultBranch,
	getRepoTags,
	getRepoForkCount,
	getRepoTree,
	getRepoLog,
	getRepoLanguages,
	type RepoContext,
} from './api/repos';
import { getIssue, getRepoIssueCount } from './api/issues';
import { fetchPullRoundPatch, getPull, getRepoPullCount } from './api/pulls';
import { getString } from './api/strings';
import { getRepoStarSummary } from './api/stars';
import { repoQueryKey, issueQueryKey, pullQueryKey, pullPatchQueryKey, issuesQueryKey, pullsQueryKey } from './api/keys';
import { resolveDefaultBranchName } from '../pages/repo/code-helpers';

export const PRELOAD_HOVER_TIME = 30;

export const usePreloader = () => {
	const queryClient = useQueryClient();

	return (queryKey: readonly unknown[], queryFn: () => Promise<any>, delayMs = PRELOAD_HOVER_TIME) => {
		let hoverTimer: ReturnType<typeof setTimeout> | undefined;

		const onMouseEnter = () => {
			hoverTimer = setTimeout(() => {
				queryClient.prefetchQuery({ queryKey, queryFn });
			}, delayMs);
		};

		const onMouseLeave = () => {
			clearTimeout(hoverTimer);
		};

		return { onMouseEnter, onMouseLeave };
	};
};

export const useRepoPreloader = () => {
	const queryClient = useQueryClient();
	const auth = useAuth();
	const preload = usePreloader();

	return (owner: string, name: string) => {
		return preload(
			['repo-prefetch', owner, name],
			async () => {
				const repo = await queryClient.fetchQuery({
					queryKey: repoQueryKey(owner, name),
					queryFn: () => getRepo(owner, name),
				});

				if (!repo) {
					return repo;
				}

				const currentDid = auth.currentDid();

				// Fetch branches and default-branch, and prefetch counts and tags concurrently
				const [branches, defaultBranchRes] = await Promise.all([
					queryClient.fetchQuery({
						queryKey: ['repo-branches', repo.repoDid],
						queryFn: () => getRepoBranches(repo),
					}),
					queryClient.fetchQuery({
						queryKey: ['repo-default-branch', repo.repoDid],
						queryFn: () => getRepoDefaultBranch(repo),
					}),
					queryClient.prefetchQuery({
						queryKey: ['repo-star-summary', repo.repoDid, currentDid],
						queryFn: () => getRepoStarSummary(repo, currentDid),
					}),
					queryClient.prefetchQuery({
						queryKey: ['repo-fork-count', repo.repoDid],
						queryFn: () => getRepoForkCount(repo),
					}),
					queryClient.prefetchQuery({
						queryKey: [...issuesQueryKey(repo.repoDid), 'count'],
						queryFn: () => getRepoIssueCount(repo),
					}),
					queryClient.prefetchQuery({
						queryKey: [...pullsQueryKey(repo.repoDid), 'count'],
						queryFn: () => getRepoPullCount(repo),
					}),
					queryClient.prefetchQuery({
						queryKey: ['repo-tags', repo.repoDid],
						queryFn: () => getRepoTags(repo),
					}),
				]);

				const defaultBranchName =
					defaultBranchRes?.name ??
					defaultBranchRes?.branch ??
					defaultBranchRes?.reference?.name ??
					resolveDefaultBranchName(branches?.branches) ??
					'main';

				// Now that we have the default branch name, prefetch tree, log and languages
				await Promise.all([
					queryClient.prefetchQuery({
						queryKey: ['repo-tree', repo.repoDid, defaultBranchName, ''],
						queryFn: async () => ({
							tree: await getRepoTree(repo, defaultBranchName, ''),
							path: '',
							ref: defaultBranchName,
						}),
					}),
					queryClient.prefetchQuery({
						queryKey: ['repo-log', repo.repoDid, defaultBranchName],
						queryFn: () => getRepoLog(repo, defaultBranchName),
					}),
					queryClient.prefetchQuery({
						queryKey: ['repo-languages', repo.repoDid, defaultBranchName],
						queryFn: () => getRepoLanguages(repo, defaultBranchName),
					}),
				]).catch(() => {
					// Ignore background prefetch errors
				});

				return repo;
			}
		);
	};
};

export const useStringPreloader = () => {
	const preload = usePreloader();
	return (actor: string, rkey: string) => {
		return preload(['string-detail', actor, rkey], () => getString(actor, rkey));
	};
};

export const useIssuePreloader = () => {
	const queryClient = useQueryClient();
	const preload = usePreloader();

	return (
		repoLabel: string,
		kind: 'issue' | 'pull',
		uri: string,
		number?: string | number,
		repoContext?: RepoContext,
	) => {
		const [owner, slug] = repoLabel.split('/');
		const issueRef = String(number || uri.split('/').pop());

		return preload(
			[kind, uri, 'prefetch'],
			async () => {
				const repo =
				repoContext ||
					(await queryClient.fetchQuery({
						queryKey: repoQueryKey(owner, slug),
						queryFn: () => getRepo(owner, slug),
					}));

				if (!repo) {
					return;
				}

				if (kind === 'issue') {
					await queryClient.prefetchQuery({
						queryKey: issueQueryKey(repo.repoDid, issueRef),
						queryFn: () => getIssue(repo, issueRef),
					});
				} else {
					const pullDetail = await queryClient.fetchQuery({
						queryKey: pullQueryKey(repo.repoDid, issueRef),
						queryFn: () => getPull(repo, issueRef),
					});

					const roundIndex = Math.max(0, pullDetail.pull.value.rounds.length - 1);
					await queryClient.prefetchQuery({
						queryKey: pullPatchQueryKey(pullDetail.pull.uri, roundIndex),
						queryFn: () => fetchPullRoundPatch(pullDetail.pull, roundIndex),
					});
				}
			},
		);
	};
};
