import { CircleDot, GitPullRequest, Globe, LoaderCircle, Rss, SquareChartGantt, Star } from 'lucide-solid';
import { A, type RouteSectionProps, useParams } from '@solidjs/router';
import { createQuery } from '@tanstack/solid-query';
import { For, Match, Show, Switch, createContext, createEffect, createMemo, createSignal, onCleanup, useContext, type Component, type JSX } from 'solid-js';
import { getRepoIssueCount } from '../../lib/api/issues';
import { getRepoPullCount } from '../../lib/api/pulls';
import { getRepo } from '../../lib/api/repos';
import { createRepoStar, deleteRepoStar, getRepoStarSummary } from '../../lib/api/stars';
import { useAuth } from '../../lib/auth';
import { useLiveEvents } from '../../lib/live-events';
import { Avatar, ErrorState, cardStyles } from '../../components/common';
import { RepoFrameSkeleton, RepoTabLink } from '../../components/repo';
import { getErrorMessage, saveRecentRepo, trimUri } from '../../lib/repo-utils';

export const repoQueryKey = (owner: string, repo: string) => ['repo', owner, repo] as const;
export const issuesQueryKey = (repoDid: string) => ['issues', repoDid] as const;
export const issueQueryKey = (repoDid: string, issueRef: string) => ['issue', repoDid, issueRef] as const;
export const pullsQueryKey = (repoDid: string) => ['pulls', repoDid] as const;
export const pullQueryKey = (repoDid: string, pullRef: string) => ['pull', repoDid, pullRef] as const;

type OptimisticStarState = { state: 'starred'; rkey?: string } | { state: 'unstarred'; rkey: string };

const createRepoQuery = () => {
	const params = useParams();
	const owner = createMemo(() => params.owner ?? '');
	const repo = createMemo(() => params.repo ?? '');

	return createQuery(() => ({
		queryKey: repoQueryKey(owner(), repo()),
		enabled: Boolean(owner() && repo()),
		queryFn: async () => getRepo(owner(), repo()),
	}));
};

type RepoQuery = ReturnType<typeof createRepoQuery>;

const RepoQueryContext = createContext<RepoQuery>();

export const RepoLayout: Component<RouteSectionProps> = (props) => {
	const repoQuery = createRepoQuery();

	return <RepoQueryContext.Provider value={repoQuery}>{props.children}</RepoQueryContext.Provider>;
};

export const useRepoQuery = () => {
	const repoQuery = useContext(RepoQueryContext);
	if (!repoQuery) {
		throw new Error('useRepoQuery must be used inside RepoLayout');
	}

	return repoQuery;
};

export const RepoFrame: Component<{
	active: 'code' | 'issues' | 'pulls';
	children: JSX.Element;
	fullWidth?: boolean;
}> = (props) => {
	const auth = useAuth();
	const live = useLiveEvents();
	const repoQuery = useRepoQuery();
	const [starError, setStarError] = createSignal<string | null>(null);
	const [starWorking, setStarWorking] = createSignal(false);
	const [optimisticStar, setOptimisticStar] = createSignal<OptimisticStarState | null>(null);
	const starSummaryQuery = createQuery(() => ({
		queryKey: ['repo-star-summary', repoQuery.data?.repoDid, auth.currentDid()],
		enabled: Boolean(repoQuery.data),
		queryFn: async () => getRepoStarSummary(repoQuery.data!, auth.currentDid()),
	}));
	const issueCountQuery = createQuery(() => ({
		queryKey: [...issuesQueryKey(repoQuery.data?.repoDid ?? ''), 'count'],
		enabled: Boolean(repoQuery.data),
		queryFn: async () => getRepoIssueCount(repoQuery.data!),
	}));
	const pullCountQuery = createQuery(() => ({
		queryKey: [...pullsQueryKey(repoQuery.data?.repoDid ?? ''), 'count'],
		enabled: Boolean(repoQuery.data),
		queryFn: async () => getRepoPullCount(repoQuery.data!),
	}));
	const starSummaryLoading = createMemo(() => !starSummaryQuery.data && (starSummaryQuery.isLoading || starSummaryQuery.isFetching));
	const starBusy = createMemo(() => starWorking() || starSummaryLoading());
	createEffect(() => {
		repoQuery.data?.repoDid;
		auth.currentDid();
		setOptimisticStar(null);
		setStarError(null);
	});
	const visibleStarSummary = createMemo(() => {
		const summary = starSummaryQuery.data;
		const optimistic = optimisticStar();
		if (!summary) {
			return optimistic?.state === 'starred'
				? {
						count: 1,
						isStarred: true,
						currentUserStarRkey: optimistic.rkey,
					}
				: summary;
		}

		if (optimistic?.state === 'starred' && !summary.isStarred) {
			return {
				...summary,
				count: summary.count + 1,
				isStarred: true,
				currentUserStarRkey: optimistic.rkey,
			};
		}

		if (optimistic?.state === 'starred') {
			return {
				...summary,
				isStarred: true,
				currentUserStarRkey: optimistic.rkey ?? summary.currentUserStarRkey,
			};
		}

		if (
			optimistic?.state !== 'unstarred' ||
			!summary.currentUserStarRkey ||
			summary.currentUserStarRkey !== optimistic.rkey
		) {
			return summary;
		}

		return {
			...summary,
			count: Math.max(0, summary.count - 1),
			isStarred: false,
			currentUserStarRkey: undefined,
		};
	});

	const star = async () => {
		const agent = auth.agent();
		const repo = repoQuery.data;
		if (!agent || !repo || starSummaryLoading() || visibleStarSummary()?.isStarred) {
			return;
		}

		setStarWorking(true);
		setStarError(null);
		setOptimisticStar({ state: 'starred' });
		try {
			const result = await createRepoStar(agent, repo);
			setOptimisticStar({ state: 'starred', rkey: result.rkey });
		} catch (cause) {
			setOptimisticStar(null);
			setStarError(getErrorMessage(cause));
		} finally {
			setStarWorking(false);
		}
	};

	const unstar = async () => {
		const agent = auth.agent();
		const rkey = visibleStarSummary()?.currentUserStarRkey;
		if (!agent || !rkey || starSummaryLoading()) {
			return;
		}

		setStarWorking(true);
		setStarError(null);
		setOptimisticStar({ state: 'unstarred', rkey });
		try {
			await deleteRepoStar(agent, rkey);
		} catch (cause) {
			setOptimisticStar(null);
			setStarError(getErrorMessage(cause));
		} finally {
			setStarWorking(false);
		}
	};

	createEffect(() => {
		const repo = repoQuery.data;
		if (!repo) {
			return;
		}

		saveRecentRepo(repo);
		live.subscribe({
			subjects: [repo.repoDid],
			sources: [
				'sh.tangled.repo.issue:repoDid',
				'sh.tangled.repo.issue:repo',
				'sh.tangled.repo.pull:target.repo',
			],
		});

		onCleanup(() => {
			live.clear();
		});
	});

	return (
		<Switch>
			<Match when={repoQuery.isLoading}>
				<div class={props.fullWidth ? 'untangled-repo-frame-full' : undefined}>
					<RepoFrameSkeleton active={props.active} />
				</div>
			</Match>
			<Match when={repoQuery.error}>
				<div class={cardStyles('p-6')}>
					<ErrorState message={getErrorMessage(repoQuery.error)} />
				</div>
			</Match>
			<Match when={repoQuery.data}>
				{(repo) => (
					<div class={props.fullWidth ? 'untangled-repo-frame-full' : undefined}>
						<section id="repo-header" class="mb-2 py-2 px-4">
							<div class="flex flex-col sm:flex-row items-start gap-4 justify-between mb-2">
								<div class="flex gap-2">
									<div class="flex items-center gap-2 flex-wrap text-lg">
										<Avatar did={repo().owner.did} size="size-8" />
										<A href={`/${repo().owner.handle}`} class="font-medium">
											{repo().owner.handle}
										</A>
										<span class="select-none">/</span>
										<A href={`/${repo().owner.handle}/${repo().slug}`} class="font-bold">
											{repo().slug}
										</A>
									</div>
								</div>

								<div class="hidden sm:block sm:flex-shrink-0">
							<div class="untangled-repo-actions">
								<div class="untangled-repo-action">
									<Show
										when={starSummaryLoading()}
										fallback={
											<Show
												when={visibleStarSummary()?.isStarred}
												fallback={
													<Show
														when={auth.agent()}
														fallback={
															<a
																href={`https://tangled.org/${repo().owner.handle}/${repo().slug}/stars`}
																target="_blank"
																rel="noreferrer"
																class="untangled-repo-action-main"
															>
																<Star class="size-4" />
																<span>star</span>
															</a>
														}
													>
														<button
															type="button"
															class="untangled-repo-action-main"
															disabled={starBusy()}
															title={starError() ?? undefined}
															onClick={() => void star()}
														>
															<Star class="size-4" />
															<span>star</span>
														</button>
													</Show>
												}
											>
												<button
													type="button"
													class="untangled-repo-action-main untangled-repo-action-starred"
													disabled={starBusy() || !visibleStarSummary()?.currentUserStarRkey}
													title={starError() ?? undefined}
													onClick={() => void unstar()}
												>
													<Star class="size-4 fill-current" />
													<span>unstar</span>
												</button>
											</Show>
										}
									>
										<button
											type="button"
											class="untangled-repo-action-main"
											disabled
											aria-label="Loading stars"
										>
											<LoaderCircle class="size-4 animate-spin" />
										</button>
									</Show>
									<Show
										when={starSummaryLoading()}
										fallback={
											<a
												href={`https://tangled.org/${repo().owner.handle}/${repo().slug}/stars`}
												target="_blank"
												rel="noreferrer"
												class="untangled-repo-action-count"
												title="Starred by"
											>
												{visibleStarSummary()?.count ?? 0}
											</a>
										}
									>
										<span class="untangled-repo-action-count" aria-disabled="true">
											-
										</span>
									</Show>
								</div>
								<a
									href={`https://tangled.org/${repo().owner.handle}/${repo().slug}/feed.atom`}
									target="_blank"
									rel="noreferrer"
									class="btn text-sm cursor-pointer list-none flex items-center gap-2"
								>
									<Rss class="size-4" />
									<span class="hidden md:inline">atom</span>
								</a>
							</div>
								</div>
							</div>

							<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-gray-600 dark:text-gray-300">
								<Show
									when={repo().record.value.description}
									fallback={<span class="italic">this repo has no description</span>}
								>
									<span>{repo().record.value.description}</span>
								</Show>

								<Show when={repo().record.value.website}>
									<a
										href={repo().record.value.website}
										target="_blank"
										rel="noreferrer"
										class="flex items-center gap-1"
									>
										<Globe class="size-4 shrink-0" />
										{trimUri(repo().record.value.website!)}
									</a>
								</Show>

								<Show when={(repo().record.value.topics ?? []).length > 0}>
									<div class="flex items-center gap-1">
										<For each={repo().record.value.topics ?? []}>
											{(topic) => <span class="untangled-topic dark:bg-gray-700">{topic}</span>}
										</For>
									</div>
								</Show>
							</div>
						</section>

						<section class="w-full flex flex-col">
							<nav class="w-full pl-4 overflow-auto">
								<div class="flex z-60">
						<RepoTabLink
							active={props.active === 'code'}
							href={`/${repo().owner.handle}/${repo().slug}`}
							icon={<SquareChartGantt class="size-4" />}
							label="overview"
						/>
									<RepoTabLink
										active={props.active === 'issues'}
										href={`/${repo().owner.handle}/${repo().slug}/issues`}
										icon={<CircleDot class="size-4" />}
										label="issues"
										meta={issueCountQuery.data === undefined ? undefined : String(issueCountQuery.data)}
									/>
								<RepoTabLink
									active={props.active === 'pulls'}
									href={`/${repo().owner.handle}/${repo().slug}/pulls`}
									icon={<GitPullRequest class="size-4" />}
									label="pulls"
									meta={pullCountQuery.data === undefined ? undefined : String(pullCountQuery.data)}
								/>
							</div>
						</nav>

							{props.children}
						</section>
					</div>
				)}
			</Match>
		</Switch>
	);
};
