import { CircleDot, GitFork, GitPullRequest, Globe, LoaderCircle, Rss, SquareChartGantt, Star } from 'lucide-solid';
import type { Did } from '@atcute/lexicons/syntax';
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, getRepoForkCount, getRepoByDid } from '../../lib/api/repos';
import { resolveActor } from '../../lib/api/identity';
import { parseAtUri } from '../../lib/api/records';
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';
import { repoQueryKey, issuesQueryKey, issueQueryKey, pullsQueryKey, pullQueryKey, pullPatchQueryKey } from '../../lib/api/keys';

export { repoQueryKey, issuesQueryKey, issueQueryKey, pullsQueryKey, pullQueryKey, pullPatchQueryKey };

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;
	loadingFallback?: JSX.Element;
}> = (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 forkCountQuery = createQuery(() => ({
		queryKey: ['repo-fork-count', repoQuery.data?.repoDid],
		enabled: Boolean(repoQuery.data),
		queryFn: async () => getRepoForkCount(repoQuery.data!),
	}));

	const sourceRepoInfo = createMemo(() => {
		const source = repoQuery.data?.record.value.source;
		if (!source) return null;
		try {
			if (source.startsWith('at://')) {
				const parsed = parseAtUri(source);
				return {
					did: parsed.did,
					rkey: parsed.rkey,
				};
			}
			if (source.startsWith('did:')) {
				return {
					did: source,
					rkey: '',
				};
			}
		} catch {
			// ignore
		}
		return null;
	});

	const sourceActorQuery = createQuery(() => {
		const info = sourceRepoInfo();
		return {
			queryKey: ['actor', info?.did],
			enabled: Boolean(info && info.rkey),
			queryFn: async () => resolveActor(info!.did),
		};
	});

	const sourceRepoQuery = createQuery(() => {
		const info = sourceRepoInfo();
		return {
			queryKey: info ? ['source-repo', info.did, info.rkey] : ['source-repo'],
			enabled: Boolean(info),
			queryFn: async () => {
				if (info!.rkey) {
					return getRepo(info!.did, info!.rkey);
				} else {
					return getRepoByDid(info!.did as Did);
				}
			},
		};
	});
	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, {
			stars: starSummaryQuery.data?.count,
			openIssues: issueCountQuery.data,
			openPRs: pullCountQuery.data,
		});
		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}>{props.loadingFallback}</RepoFrameSkeleton>
				</div>
			</Match>
			<Match when={repoQuery.error}>
				<div class={cardStyles('p-6')}>
					<ErrorState message={getErrorMessage(repoQuery.error)} />
				</div>
			</Match>
			<Match when={repoQuery.data}>
				{(repo) => {
					const renderRepoActions = () => (
						<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>
							<div class="untangled-repo-action">
								<A
									href={`/${repo().owner.handle}/${repo().slug}/fork`}
									class="untangled-repo-action-main"
								>
									<GitFork class="size-4" />
									<span>fork</span>
								</A>
								<Show
									when={forkCountQuery.isLoading}
									fallback={
										<a
											href={`https://tangled.org/${repo().owner.handle}/${repo().slug}/forks`}
											target="_blank"
											rel="noreferrer"
											class="untangled-repo-action-count"
											title="Forked by"
										>
											{forkCountQuery.data ?? 0}
										</a>
									}
								>
									<span class="untangled-repo-action-count" aria-disabled="true">
										<LoaderCircle class="size-3 animate-spin" />
									</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>
					);

					return (
						<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 flex-col gap-0.5">
										<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>
										<Show when={sourceRepoInfo()}>
											{(info) => (
												<div class="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400 ml-10">
													<GitFork class="size-3 shrink-0" />
													<span>forked from</span>
													<Show
														when={sourceRepoQuery.data}
														fallback={
															<span class="font-semibold">
																{info().rkey
																	? `${sourceActorQuery.data?.handle ?? info().did}/${info().rkey}`
																	: info().did}
															</span>
														}
													>
														{(source) => (
															<A
																href={`/${source().owner.handle}/${source().slug}`}
																class="hover:underline font-semibold"
															>
																{source().owner.handle}/{source().record.value.name || source().slug}
															</A>
														)}
													</Show>
												</div>
											)}
										</Show>
									</div>

									<div class="hidden sm:block sm:flex-shrink-0">
										{renderRepoActions()}
									</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>

								<div class="block sm:hidden mt-4">
									{renderRepoActions()}
								</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)}
										reserveMeta
									/>
								<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)}
									reserveMeta
								/>
							</div>
						</nav>

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