import clsx from 'clsx';
import { File, Folder, GitBranch, GitCommitHorizontal, List } from 'lucide-solid';
import { A, useNavigate, useParams } from '@solidjs/router';
import { createQuery, keepPreviousData, useQueryClient } from '@tanstack/solid-query';
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js';
import { buildBlobDataUrl, decodeBlobText, getRepoBlob, getRepoBranches, getRepoDefaultBranch, getRepoLanguages, getRepoLog, getRepoTags, getRepoTree } from '../../lib/api/repos';
import { Avatar, ErrorState, PlaceholderAvatar, buttonStyles, cardStyles } from '../../components/common';
import { CloneDropdown, CodeView, FileRow, MarkdownBlock, OverviewFileRow, ReadmeCard, RepoTreeSkeleton } from '../../components/repo';
import { RepoFrame, useRepoQuery } from './shared';
import { blobHref, commitHref, commitsHref, countLines, decodeRoutePath, formatBytes, formatLanguagePercent, formatRelativeTime, getErrorMessage, getParentPath, imageLike, isDirectory, joinPath, languageColor, markdownLike, safeDecode, sortedTreeEntries, svgLike, treeHref, videoLike } from '../../lib/repo-utils';
import { LOADING_DELAY_MS, hasNamedRef, normalizeLanguages, resolveDefaultBranchName, resolveRouteRefAndPath, shouldAnimateNavigation, useDelayedLoading } from './code-helpers';

const PathBreadcrumbs: Component<{
	repo: Parameters<typeof treeHref>[0];
	refName: string;
	path: string;
	withDivider?: boolean;
}> = (props) => {
	const segments = createMemo(() => props.path.split('/').filter(Boolean));

	return (
		<div class={clsx('text-base', props.withDivider && 'pb-2 mb-3 border-b border-gray-200 dark:border-gray-700')}>
			<div class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
				<A href={treeHref(props.repo, props.refName)} class="text-gray-500 dark:text-gray-400 hover:underline">
					{props.repo.slug}
				</A>
				<For each={segments()}>
					{(segment, index) => (
						<>
							<span class="px-1">/</span>
							<Show
								when={index() < segments().length - 1}
								fallback={<span class="text-gray-900 dark:text-gray-100">{segment}</span>}
							>
								<A
									href={treeHref(props.repo, props.refName, segments().slice(0, index() + 1).join('/'))}
									class="text-gray-500 dark:text-gray-400 hover:underline"
								>
									{segment}
								</A>
							</Show>
						</>
					)}
				</For>
			</div>
		</div>
	);
};

const commitSummary = (message: string) => message.trim().split('\n')[0] || 'no commit message';

const readmeIsAvailable = (
	readme: { filename: string; contents: string } | undefined,
	entries: Array<{ name: string; mode: string }>,
) => {
	if (!readme) return false;
	if (readme.contents.trim().length > 0) return true;

	const filename = readme.filename.trim().toLowerCase();
	return filename.length > 0 && entries.some((entry) => !entry.mode.startsWith('004') && entry.name.toLowerCase() === filename);
};

const TreeLatestCommitPanel: Component<{
	repo: Parameters<typeof treeHref>[0];
	commit: {
		hash: string;
		message: string;
		when: string;
		author?: {
			email: string;
			name: string;
		};
	};
}> = (props) => (
	<div class="pb-2 mb-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between flex-wrap text-sm gap-2">
		<div class="flex min-w-0 flex-wrap items-center gap-1">
			<Show
				when={props.commit.author?.email?.startsWith('did:') ? props.commit.author.email : undefined}
				fallback={<PlaceholderAvatar size="size-6" iconSize="size-4" />}
			>
				{(did) => <Avatar did={did()} size="size-6" />}
			</Show>
			<span class="font-medium text-gray-700 dark:text-gray-200">{props.commit.author?.name || 'unknown author'}</span>
			<span class="select-none text-gray-400 dark:text-gray-500">·</span>
			<A
				href={`/${props.repo.owner.handle}/${props.repo.slug}/commit/${props.commit.hash}`}
				class="min-w-0 truncate no-underline hover:underline text-gray-900 dark:text-white"
			>
				{commitSummary(props.commit.message)}
			</A>
			<span class="select-none text-gray-400 dark:text-gray-500">·</span>
			<span class="text-gray-500 dark:text-gray-400">{formatRelativeTime(props.commit.when)}</span>
		</div>
		<A
			href={`/${props.repo.owner.handle}/${props.repo.slug}/commit/${props.commit.hash}`}
			class="inline-flex w-fit items-center rounded bg-gray-100 px-2 py-1 font-mono text-xs text-gray-700 no-underline hover:underline dark:bg-gray-900 dark:text-gray-300"
		>
			{props.commit.hash.slice(0, 8)}
		</A>
	</div>
);

const FallbackLanguageBar: Component = () => (
	<details class="group -my-4 -m-6 mb-4">
		<summary class="untangled-language-bar">
			<div
				class="untangled-language-segment"
				title="Other 100%"
				style={{
					width: "100%",
					'background-color': languageColor("Other"),
				}}
			/>
		</summary>
	</details>
);

void FallbackLanguageBar;

const RepoLanguageBar: Component<{
	languages: Array<{ name: string; percentage: number; color?: string }>;
}> = (props) => (
	<Show when={props.languages.length > 0}>
		<details class="group -my-4 -m-6 mb-4">
			<summary class="untangled-language-bar">
				<For each={props.languages}>
					{(language) => {
						const name = language.name || 'Other';
						const color = language.color || languageColor(name);
						return (
							<div
								class="untangled-language-segment"
								title={`${name} ${formatLanguagePercent(language.percentage)}`}
								style={{
									width: `${language.percentage}%`,
									'background-color': color,
								}}
							/>
						);
					}}
				</For>
			</summary>
			<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-4 flex-wrap">
				<For each={props.languages}>
					{(language) => {
						const name = language.name || 'Other';
						const color = language.color || languageColor(name);
						return (
							<div class="flex flex-grow items-center justify-center gap-2 text-xs">
								<div
									class="size-2 rounded-full"
									style={{
										background: `radial-gradient(circle at 35% 35%, color-mix(in srgb, ${color} 70%, white), ${color} 30%, color-mix(in srgb, ${color} 85%, black))`,
									}}
								/>
								<div>
									{name}{' '}
									<span class="text-gray-500 dark:text-gray-400">
										{formatLanguagePercent(language.percentage)}
									</span>
								</div>
							</div>
						);
					}}
				</For>
			</div>
		</details>
	</Show>
);

const LoadingFilePath: Component<{
	repo: Parameters<typeof treeHref>[0];
	refName: string;
	path: string;
}> = (props) => {
	const segments = createMemo(() => props.path.split('/').filter(Boolean));
	const filename = createMemo(() => segments().at(-1) ?? props.path);

	return (
		<div class="untangled-blob-card">
			<div class="untangled-blob-header pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
				<div class="flex flex-col md:flex-row md:justify-between gap-2">
					<PathBreadcrumbs repo={props.repo} refName={props.refName} path={props.path} />
					<div class="untangled-blob-file-info text-gray-500 md:text-sm flex flex-wrap md:gap-0 items-center">
						<span>
							at <A href={treeHref(props.repo, props.refName)}>{props.refName}</A>
						</span>
					</div>
				</div>
			</div>
			<div class="space-y-3">
				<OverviewFileRow
					name={filename()}
					href={blobHref(props.repo, props.refName, props.path)}
					icon={<File class="size-4 flex-shrink-0" />}
					class="untangled-file-row-loading"
				/>
			</div>
		</div>
	);
};

const RepoCodePageLegacy: Component = () => {
	const params = useParams();
	const navigate = useNavigate();
	const repoQuery = useRepoQuery();
	const ref = createMemo(() => safeDecode(params.ref ?? 'HEAD'));
	const path = createMemo(() => decodeRoutePath(params.path));

	const codeQuery = createQuery(() => {
		const repo = repoQuery.data;
		return {
			queryKey: ['repo-code', repo?.repoDid, ref(), path()],
			enabled: Boolean(repo),
			queryFn: async () => {
				const currentRepo = repo!;
				const [tree, branches, tags, log] = await Promise.all([
					getRepoTree(currentRepo, ref(), path()),
					getRepoBranches(currentRepo),
					getRepoTags(currentRepo),
					getRepoLog(currentRepo, ref()),
				]);
				const defaultBranchName = branches.branches.find((branch) => branch.is_default)?.reference.name;
				const languageRef = ref() === 'HEAD' ? (defaultBranchName ?? ref()) : ref();
				const languages = await getRepoLanguages(currentRepo, languageRef);

				return { tree, branches, tags, languages, log };
			},
		};
	});

	return (
		<RepoFrame active="code">
			<Switch>
				<Match when={codeQuery.isLoading}>
					<RepoTreeSkeleton />
				</Match>
				<Match when={codeQuery.error}>
					<div class={cardStyles('p-6')}>
						<ErrorState message={getErrorMessage(codeQuery.error)} />
					</div>
				</Match>
				<Match when={Boolean(codeQuery.data && repoQuery.data)}>
					{(() => {
						const repo = repoQuery.data!;
						const data = codeQuery.data!;
						const languages = normalizeLanguages(data.languages?.languages ?? [], data.languages?.totalSize);
						const branches = data.branches?.branches ?? [];
						const tags = data.tags?.tags ?? [];
						const commits = data.log?.commits ?? [];
						const segments = path() ? path().split('/') : [];
						const defaultBranch =
							branches.find((branch) => branch.is_default)?.reference.name || ref();
						const parentPath = data.tree.dotdot ?? getParentPath(path());
						const sortedFiles = sortedTreeEntries(data.tree.files ?? []);
						const latestCommit =
							data.tree.lastCommit ??
							(commits[0]
							? {
									hash: commits[0].this,
									message: commits[0].message,
									when: commits[0].committer?.When || commits[0].author?.When || '',
									author: commits[0].author
										? {
												email: commits[0].author.Email,
												name: commits[0].author.Name,
												when: commits[0].author.When,
											}
										: undefined,
								}
							: undefined);

							return (
								<>
									<div class="tree">
									<RepoLanguageBar languages={languages} />

									<div class="flex items-center justify-between pb-5 gap-3 flex-wrap">
										<div class="flex gap-2 items-center">
											<select
												class="p-1 border max-w-32 border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"
												value={ref()}
												onChange={(event) =>
													navigate(treeHref(repo, event.currentTarget.value, path()))
												}
											>
												<optgroup label={`branches (${branches.length})`}>
													<For each={branches}>
														{(branch) => <option value={branch.reference.name}>{branch.reference.name}</option>}
													</For>
												</optgroup>
												<optgroup label={`tags (${tags.length})`}>
													<Show
														when={tags.length > 0}
														fallback={<option disabled>no tags found</option>}
													>
														<For each={tags.slice(0, 40)}>
															{(tag) => <option value={tag.name}>{tag.name}</option>}
														</For>
													</Show>
												</optgroup>
											</select>
											<Show when={ref() !== defaultBranch}>
												<button
													type="button"
													class={buttonStyles()}
													onClick={() => navigate(treeHref(repo, defaultBranch))}
												>
													default
												</button>
											</Show>
										</div>

											<div class="flex items-center gap-4 text-sm font-medium">
												<span class="inline-flex items-center gap-1">
													<GitCommitHorizontal class="size-4" />
													{data.log.total ?? commits.length}
												</span>
												<span class="inline-flex items-center gap-1">
													<GitBranch class="size-4" />
													{branches.length}
												</span>
												<span class="inline-flex items-center gap-1">
													<span>#</span>
													{tags.length}
												</span>
											</div>
										</div>

									<Show when={segments.length > 0}>
										<div class="pb-3 mb-3 border-b border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400 overflow-auto whitespace-nowrap">
											<A href={treeHref(repo, ref())} class="hover:underline">
												{repo.slug}
											</A>
											<For each={segments}>
												{(segment, index) => (
													<>
														<span class="px-1">/</span>
														<Show
															when={index() < segments.length - 1}
															fallback={<span class="text-gray-900 dark:text-gray-100">{segment}</span>}
														>
															<A
																href={treeHref(repo, ref(), segments.slice(0, index() + 1).join('/'))}
																class="hover:underline"
															>
																{segment}
															</A>
														</Show>
													</>
												)}
											</For>
											</div>
										</Show>

										<Show when={latestCommit}>
											{(commit) => (
												<div class="pb-2 mb-3 border-b border-gray-200 dark:border-gray-700 flex items-center flex-wrap gap-2">
													<div class="flex flex-wrap gap-1">
														<span class="text-sm text-gray-700 dark:text-gray-300">
															{commit().message.trim().split('\n')[0]}
														</span>
													</div>
													<span class="text-xs text-gray-500 dark:text-gray-400">
														{commit().hash.slice(0, 8)} · {formatRelativeTime(commit().when)}
													</span>
												</div>
											)}
										</Show>

										<div class="flex flex-col divide-y divide-gray-100 dark:divide-gray-700/60">
											<Show when={parentPath !== null}>
												<FileRow
													name=".."
													href={treeHref(repo, ref(), parentPath ?? '')}
													icon={<Folder class="size-4 flex-shrink-0" />}
												/>
											</Show>
												<For each={sortedFiles}>
												{(entry) => (
													<FileRow
														name={entry.name}
														href={
															isDirectory(entry)
																? treeHref(repo, ref(), joinPath(path(), entry.name))
																: blobHref(repo, ref(), joinPath(path(), entry.name))
														}
														icon={
															isDirectory(entry) ? (
																<Folder class="size-4 flex-shrink-0" />
															) : (
																<File class="size-4 flex-shrink-0" />
															)
														}
														lastCommit={entry.last_commit}
														lastCommitHref={entry.last_commit ? commitHref(repo, entry.last_commit.hash) : undefined}
														commitMessage={entry.last_commit?.message}
													/>
												)}
											</For>
										</div>
									</div>

								<Show when={readmeIsAvailable(data.tree.readme, sortedFiles) && data.tree.readme}>
									{(readme) => <ReadmeCard filename={readme().filename} markdown={readme().contents} />}
								</Show>
								</>
							);
						})()}
				</Match>
			</Switch>
		</RepoFrame>
	);
};

void RepoCodePageLegacy;

export const RepoCodePage: Component = () => {
	const params = useParams();
	const navigate = useNavigate();
	const queryClient = useQueryClient();
	const repoQuery = useRepoQuery();
	const [pendingTreeHref, setPendingTreeHref] = createSignal<string>();
	let navigationAttempt = 0;
	const routeRef = createMemo(() => safeDecode(params.ref ?? 'HEAD'));
	const routePath = createMemo(() => decodeRoutePath(params.path));

	const branchesQuery = createQuery(() => {
		const repo = repoQuery.data;
		return {
			queryKey: ['repo-branches', repo?.repoDid],
			enabled: Boolean(repo),
			queryFn: async () => getRepoBranches(repo!),
		};
	});
	const defaultBranchQuery = createQuery(() => {
		const repo = repoQuery.data;
		return {
			queryKey: ['repo-default-branch', repo?.repoDid],
			enabled: Boolean(repo),
			queryFn: async () => getRepoDefaultBranch(repo!),
		};
	});
	const defaultBranch = createMemo(
		() =>
			defaultBranchQuery.data?.name ??
			defaultBranchQuery.data?.branch ??
			defaultBranchQuery.data?.reference?.name ??
			resolveDefaultBranchName(branchesQuery.data?.branches),
	);
	const defaultBranchReady = createMemo(() => Boolean(defaultBranchQuery.data || defaultBranchQuery.error || branchesQuery.data));
	const routeTarget = createMemo(() =>
		resolveRouteRefAndPath(
			routeRef(),
			routePath(),
			branchesQuery.data?.branches.map((branch) => branch.reference.name),
		),
	);
	const ref = createMemo(() => routeTarget().ref);
	const path = createMemo(() => routeTarget().path);
	const effectiveRef = createMemo(() => (ref() === 'HEAD' ? (defaultBranch() ?? (defaultBranchReady() ? 'HEAD' : undefined)) : ref()));
	const treeQuery = createQuery(() => {
		const repo = repoQuery.data;
		const currentRef = effectiveRef();
		return {
			queryKey: ['repo-tree', repo?.repoDid, currentRef, path()],
			enabled: Boolean(repo && currentRef),
			queryFn: async () => ({
				tree: await getRepoTree(repo!, currentRef!, path()),
				path: path(),
				ref: currentRef!,
			}),
			placeholderData: keepPreviousData,
		};
	});
	const selectedTreeRef = createMemo(() => {
		const currentTreeRef = treeQuery.data?.ref;
		if (currentTreeRef && currentTreeRef !== 'HEAD') return currentTreeRef;

		const currentRouteRef = ref();
		return currentRouteRef === 'HEAD' ? (defaultBranch() ?? currentRouteRef) : currentRouteRef;
	});
	const tagsQuery = createQuery(() => {
		const repo = repoQuery.data;
		return {
			queryKey: ['repo-tags', repo?.repoDid],
			enabled: Boolean(repo),
			queryFn: async () => getRepoTags(repo!),
		};
	});
	const logQuery = createQuery(() => {
		const repo = repoQuery.data;
		const currentRef = effectiveRef();
		return {
			queryKey: ['repo-log', repo?.repoDid, currentRef],
			enabled: Boolean(repo && currentRef),
			queryFn: async () => getRepoLog(repo!, currentRef!),
			placeholderData: keepPreviousData,
		};
	});
	const languageRef = effectiveRef;
	const languagesQuery = createQuery(() => {
		const repo = repoQuery.data;
		const currentLanguageRef = languageRef();
		return {
			queryKey: ['repo-languages', repo?.repoDid, currentLanguageRef],
			enabled: Boolean(repo && currentLanguageRef),
			queryFn: async () => getRepoLanguages(repo!, currentLanguageRef!),
			placeholderData: keepPreviousData,
		};
	});
	const overviewError = createMemo(
		() => treeQuery.error || branchesQuery.error || tagsQuery.error || logQuery.error || languagesQuery.error,
	);
	const overviewReady = createMemo(() =>
		Boolean(repoQuery.data && treeQuery.data && branchesQuery.data && tagsQuery.data && logQuery.data),
	);
	const showOverviewSkeleton = useDelayedLoading(() => !overviewReady() && !overviewError());

	createEffect(() => {
		if (!treeQuery.isFetching) setPendingTreeHref(undefined);
	});

	const handleFileClick = (
		event: MouseEvent,
		href: string,
		repo: Parameters<typeof getRepoBlob>[0],
		refName: string,
		filePath: string,
	) => {
		if (!shouldAnimateNavigation(event)) return;

		event.preventDefault();
		const attempt = ++navigationAttempt;
		setPendingTreeHref(href);

		const delay = new Promise<'delay'>((resolve) => {
			setTimeout(() => resolve('delay'), LOADING_DELAY_MS);
		});
		const load = queryClient
			.ensureQueryData({
				queryKey: ['blob', repo.repoDid, refName, filePath],
				queryFn: async () => getRepoBlob(repo, refName, filePath),
			})
			.then(
				() => 'loaded' as const,
				() => 'loaded' as const,
			);

		void Promise.race([delay, load]).then((result) => {
			if (attempt !== navigationAttempt) return;
			if (result === 'loaded') setPendingTreeHref(undefined);
			navigate(href);
		});
	};

	return (
		<RepoFrame active="code">
			<Switch>
				<Match when={!overviewReady() && !overviewError() && showOverviewSkeleton()}>
					<RepoTreeSkeleton />
				</Match>
				<Match when={overviewError()}>
					<div class={cardStyles('p-6')}>
						<ErrorState message={getErrorMessage(overviewError())} />
					</div>
				</Match>
				<Match when={overviewReady()}>
					{(() => {
						const repo = repoQuery.data!;
						const treeData = treeQuery.data!;
						const tree = treeData.tree;
								const languages = normalizeLanguages(languagesQuery.data?.languages ?? [], languagesQuery.data?.totalSize);
						const branches = branchesQuery.data?.branches ?? [];
							const tags = tagsQuery.data?.tags ?? [];
								const commits = logQuery.data?.commits ?? [];
								const activeRef = selectedTreeRef();
								const currentDefaultBranch = defaultBranch() ?? activeRef;
								const activeRefInOptions = hasNamedRef(activeRef, branches, tags);
							const treeRef = activeRef;
							const treePath = treeData.path;
							const parentPath = tree.dotdot ?? getParentPath(treePath);
							const sortedFiles = sortedTreeEntries(tree.files ?? []);
							const isNestedTreePath = treePath.length > 0;

							return (
								<>
								<section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full mx-auto dark:text-white">
									<RepoLanguageBar languages={languages} />

									<div class="flex items-center justify-between gap-4 pb-5">
										<div class="flex items-center gap-2">
												<select
													class="untangled-branch-select min-w-32 rounded border border-gray-300 bg-white px-3 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
													value={selectedTreeRef()}
													ref={(element) => {
														createEffect(() => {
															const nextValue = selectedTreeRef();
															branchesQuery.data?.branches.length;
															tagsQuery.data?.tags.length;
															queueMicrotask(() => {
																element.value = nextValue;
															});
														});
													}}
													onChange={(event) => navigate(treeHref(repo, event.currentTarget.value, path()))}
												>
													<Show when={!activeRefInOptions}>
														<option value={activeRef} selected>
															{activeRef}
														</option>
													</Show>
													<optgroup label={`branches (${branches.length})`}>
														<For each={branches}>
															{(branch) => (
																<option value={branch.reference.name} selected={branch.reference.name === activeRef}>
																	{branch.reference.name}
																</option>
															)}
														</For>
													</optgroup>
													<optgroup label={`tags (${tags.length})`}>
														<Show when={tags.length > 0} fallback={<option disabled>no tags found</option>}>
															<For each={tags.slice(0, 40)}>
																{(tag) => (
																	<option value={tag.name} selected={tag.name === activeRef}>
																		{tag.name}
																	</option>
																)}
															</For>
														</Show>
													</optgroup>
												</select>
											<button type="button" class={buttonStyles()} title="Compare branches or tags">
												<GitBranch class="size-4" />
											</button>
										</div>

											<CloneDropdown repo={repo} refName={activeRef} />
									</div>

									<Show when={isNestedTreePath}>
										<div>
											<PathBreadcrumbs repo={repo} refName={treeRef} path={treePath} withDivider />
											<Show when={tree.lastCommit}>
												{(commit) => <TreeLatestCommitPanel repo={repo} commit={commit()} />}
											</Show>
										</div>
									</Show>

										<div class={clsx('untangled-repo-overview', isNestedTreePath && 'untangled-repo-overview--path')}>
											<div class="untangled-file-tree-pane min-w-0 pr-0 md:pr-6">
												<div class="space-y-3">
													<Show when={parentPath !== null}>
														<OverviewFileRow
															name=".."
															href={treeHref(repo, treeRef, parentPath ?? '')}
															icon={<Folder class="size-4 flex-shrink-0 fill-current" />}
															class={pendingTreeHref() === treeHref(repo, treeRef, parentPath ?? '') ? 'untangled-file-row-loading' : undefined}
															onClick={(event) => {
																if (shouldAnimateNavigation(event)) {
																	navigationAttempt += 1;
																	setPendingTreeHref(treeHref(repo, treeRef, parentPath ?? ''));
																}
															}}
														/>
													</Show>
													<For each={sortedFiles}>
														{(entry) => {
															const entryPath = joinPath(treePath, entry.name);
															const directory = isDirectory(entry);
															const href = directory ? treeHref(repo, treeRef, entryPath) : blobHref(repo, treeRef, entryPath);
															const currentRef = effectiveRef();

															let hoverTimer: ReturnType<typeof setTimeout> | undefined;

															const handleMouseEnter = () => {
																hoverTimer = setTimeout(() => {
																	if (!directory) {
																		queryClient.prefetchQuery({
																			queryKey: ['blob', repo.repoDid, treeRef, entryPath],
																			queryFn: () => getRepoBlob(repo, treeRef, entryPath),
																		});
																		return;
																	}

																	queryClient.prefetchQuery({
																		queryKey: ['repo-tree', repo.repoDid, currentRef, entryPath],
																		queryFn: async () => ({
																			tree: await getRepoTree(repo, currentRef!, entryPath),
																			path: entryPath,
																			ref: currentRef!,
																		}),
																	});
																}, 30);
															};
															const handleMouseLeave = () => {
																clearTimeout(hoverTimer);
															};

															return (
																<OverviewFileRow
																	name={entry.name}
																	href={href}
																	icon={
																		directory ? (
																			<Folder class="size-4 flex-shrink-0 fill-current" />
																		) : (
																			<File class="size-4 flex-shrink-0" />
																		)
																	}
																	lastCommit={entry.last_commit}
																	lastCommitHref={entry.last_commit ? commitHref(repo, entry.last_commit.hash) : undefined}
																	class={pendingTreeHref() === href ? 'untangled-file-row-loading' : undefined}
																	onMouseEnter={handleMouseEnter}
																	onMouseLeave={handleMouseLeave}
																	onClick={(event) => {
																		if (directory) {
																			if (shouldAnimateNavigation(event)) {
																				navigationAttempt += 1;
																				setPendingTreeHref(href);
																			}
																			return;
																		}

																		handleFileClick(event, href, repo, treeRef, entryPath);
																	}}
																/>
															);
														}}
													</For>
												</div>
											</div>

											<Show when={!isNestedTreePath}>
												<div class="min-w-0 md:pt-0 md:pl-6 border-gray-100 dark:border-gray-700 md:border-t-0 md:border-l">
													<A href={commitsHref(repo, activeRef)} class="flex items-center gap-2 pb-3 font-bold no-underline hover:underline dark:text-white">
														<List class="size-4" />
														<span>commits</span>
														<span class="rounded bg-gray-200 px-1 py-0.5 text-sm font-normal dark:bg-gray-700">
															{logQuery.data?.total ?? commits.length}
														</span>
													</A>
													<div class="space-y-6">
														<For each={commits.slice(0, 6)}>
															{(commit) => (
																<div>
																	<div id="commit-message">
																		<div class="text-base cursor-pointer">
																			<div><div>
																				<a
																					href={`/${repo.owner.handle}/${repo.slug}/commit/${commit.this}`}
																					class="inline no-underline hover:underline dark:text-white"
																				>
																					{commit.message.trim().split('\n')[0]}
																				</a>
																			</div></div>
																		</div>
																	</div>
																	<div class="text-xs mt-2 text-gray-500 dark:text-gray-400 flex items-center flex-wrap">
																		<span class="font-mono">
																			<a
																				href={`/${repo.owner.handle}/${repo.slug}/commit/${commit.this}`}
																				class="no-underline hover:underline bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 px-2 py-1 rounded flex items-center gap-2"
																			>
																				{commit.this.slice(0, 8)}
																				<GitCommitHorizontal class="w-3 h-3" />
																			</a>
																		</span>
																		<span class="mx-1 before:content-['·'] before:select-none" />
																		<span class="flex items-center gap-1">
																			<Show
																				when={commit.author?.Email?.startsWith('did:') ? commit.author.Email : undefined}
																				fallback={<PlaceholderAvatar size="size-6" iconSize="size-4" />}
																			>
																				{(did) => <Avatar did={did()} size="size-6" />}
																			</Show>
																			<span>{commit.author?.Name || commit.committer?.Name || 'unknown author'}</span>
																		</span>
																		<div class="inline-block px-1 select-none after:content-['·']" />
																		<time datetime={commit.committer?.When || commit.author?.When || ''}>
																			{formatRelativeTime(commit.committer?.When || commit.author?.When || '')}
																		</time>
																		<Show when={activeRef === currentDefaultBranch}>
																			<div class="inline-block px-1 select-none after:content-['·']" />
																			<span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-[2px] inline-flex items-center">
																				{currentDefaultBranch}
																			</span>
																		</Show>
																	</div>
																</div>
															)}
														</For>
													</div>

													<div class="mt-6 border-t border-gray-200 pt-4 dark:border-gray-700">
														<div class="flex items-center gap-2 pb-2 font-bold">
															<GitBranch class="size-4" />
															<span>branches</span>
															<span class="rounded bg-gray-200 px-1 py-0.5 text-sm font-normal dark:bg-gray-700">
																{branches.length}
															</span>
														</div>
														<div class="space-y-1 text-sm">
															<For each={branches.slice(0, 3)}>
																{(branch) => (
																	<div class="flex items-center justify-between gap-2">
																		<span>
																			{branch.reference.name}
																			<Show when={branch.is_default}>
																				<span class="ml-2 rounded bg-gray-200 px-1 py-0.5 font-mono text-xs dark:bg-gray-700">
																					default
																				</span>
																			</Show>
																		</span>
																		<span class="text-xs text-gray-500 dark:text-gray-400">
																			{branch.commit?.Committer?.When ? formatRelativeTime(branch.commit.Committer.When) : ''}
																		</span>
																	</div>
																)}
															</For>
														</div>
													</div>
												</div>
											</Show>
										</div>
									</section>

						<Show when={readmeIsAvailable(tree.readme, sortedFiles) && tree.readme}>
							{(readme) => <ReadmeCard filename={readme().filename} markdown={readme().contents} />}
						</Show>
							</>
						);
					})()}
				</Match>
			</Switch>
		</RepoFrame>
	);
};

export const BlobPage: Component = () => {
	const params = useParams();
	const repoQuery = useRepoQuery();
	const [showRendered, setShowRendered] = createSignal(true);
	const [wrapContent, setWrapContent] = createSignal(false);
	const ref = createMemo(() => safeDecode(params.ref));
	const path = createMemo(() => decodeRoutePath(params.path));

	const blobQuery = createQuery(() => {
		const repo = repoQuery.data;
		return {
			queryKey: ['blob', repo?.repoDid, ref(), path()],
			enabled: Boolean(repo && path()),
			queryFn: async () => getRepoBlob(repo!, ref(), path()),
		};
	});

	createEffect(() => {
		const blob = blobQuery.data;
		setWrapContent(false);
		setShowRendered(Boolean(blob && (markdownLike(path(), blob.mimeType) || svgLike(path(), blob.mimeType))));
	});

	return (
		<RepoFrame active="code">
			<Switch>
				<Match when={blobQuery.isLoading && repoQuery.data}>
					<LoadingFilePath repo={repoQuery.data!} refName={ref()} path={path()} />
				</Match>
				<Match when={blobQuery.error}>
					<div class={cardStyles('p-6')}>
						<ErrorState message={getErrorMessage(blobQuery.error)} />
					</div>
				</Match>
				<Match when={Boolean(blobQuery.data && repoQuery.data)}>
					{(() => {
						const repo = repoQuery.data!;
						const blob = blobQuery.data!;
						const text = createMemo(() => decodeBlobText(blob));
						const imageUrl = createMemo(() => buildBlobDataUrl(blob));
						const rawUrl = createMemo(() => buildBlobDataUrl(blob));
						const isMarkdown = createMemo(() => markdownLike(path(), blob.mimeType));
						const isSvg = createMemo(() => svgLike(path(), blob.mimeType));
						const isVideo = createMemo(() => videoLike(path(), blob.mimeType));
						const isImage = createMemo(() => imageLike(path(), blob.mimeType));
							const showingText = createMemo(
								() => blob.content !== undefined && !blob.fileTooLarge && (!blob.isBinary || isSvg()),
							);
							const lineCount = createMemo(() => countLines(text()));

							return (
								<div class="untangled-blob-card peer">
									<div class="untangled-blob-header pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
									<div class="flex flex-col md:flex-row md:justify-between gap-2">
											<PathBreadcrumbs repo={repo} refName={ref()} path={path()} />

											<div class="untangled-blob-file-info text-gray-500 md:text-sm flex flex-wrap md:gap-0 items-center">
												<span>at <A href={treeHref(repo, ref())}>{ref()}</A></span>
												<Show when={showingText()}>
													<span class="select-none px-1 md:px-2">·</span>
													<span>{lineCount()} lines</span>
												</Show>
												<Show when={blob.size}>
													<span class="select-none px-1 md:px-2">·</span>
													<span>{formatBytes(blob.size!)}</span>
												</Show>
												<Show when={rawUrl()}>
													<span class="select-none px-1 md:px-2">·</span>
													<a href={rawUrl()!} target="_blank" rel="noreferrer" class="hover:underline">
														view raw
													</a>
												</Show>
												<Show when={isMarkdown() || isSvg()}>
													<span class="select-none px-1 md:px-2">·</span>
													<button type="button" onClick={() => setShowRendered((value) => !value)}>
														view {showRendered() ? 'code' : 'rendered'}
													</button>
												</Show>
												<Show when={showingText()}>
													<div class="hidden">
														<span class="select-none px-1 md:px-2">·</span>
														<label class="flex lowercase font-normal px-1 py-0 gap-1 text-xs md:text-sm">
															<input
																type="checkbox"
																checked={wrapContent()}
																onChange={(event) => setWrapContent(event.currentTarget.checked)}
															/>
															wrap content
														</label>
													</div>
												</Show>
											</div>
										</div>
									</div>

									<Show when={blob.lastCommit}>
										{(commit) => (
											<div class="untangled-blob-commit-panel">
												<div class="flex min-w-0 flex-wrap items-center gap-1">
													<Show
														when={commit().author?.email?.startsWith('did:') ? commit().author!.email : undefined}
														fallback={<PlaceholderAvatar size="size-6" iconSize="size-4" />}
													>
														{(did) => <Avatar did={did()} size="size-6" />}
													</Show>
													<span class="font-medium text-gray-700 dark:text-gray-200">
														{commit().author?.name || 'unknown author'}
													</span>
													<span class="select-none text-gray-400 dark:text-gray-500">·</span>
													<span class="truncate text-gray-900 dark:text-gray-100">{commit().message.trim().split('\n')[0]}</span>
													<span class="select-none text-gray-400 dark:text-gray-500">·</span>
													<span class="text-gray-400 dark:text-gray-500">{formatRelativeTime(commit().when)}</span>
												</div>
												<A
													href={`/${repo.owner.handle}/${repo.slug}/commit/${commit().hash}`}
													class="untangled-blob-commit-link"
												>
													{commit().hash.slice(0, 8)}
												</A>
											</div>
										)}
									</Show>

									<Switch>
									<Match when={blob.submodule}>
										<p class="untangled-blob-message text-center text-gray-400 dark:text-gray-500">
											This directory is a git submodule of{' '}
											<a href={blob.submodule!.url} class="underline">
												{blob.submodule!.url}
											</a>
											.
										</p>
									</Match>
									<Match when={blob.fileTooLarge}>
										<p class="untangled-blob-message text-center text-gray-400 dark:text-gray-500">
											This file is too large to render.
											</p>
										</Match>
										<Match when={isVideo() && rawUrl()}>
											<div class="untangled-blob-preview text-center">
												<video controls class="max-w-full h-auto mx-auto border border-gray-200 dark:border-gray-700 rounded">
													<source src={rawUrl()!} />
												</video>
											</div>
										</Match>
										<Match when={isImage() && imageUrl() && (!isSvg() || showRendered())}>
											<div class="untangled-blob-preview text-center">
												<img src={imageUrl()!} alt={path()} class="max-w-full h-auto mx-auto rounded border border-gray-200 dark:border-gray-700" />
											</div>
										</Match>
										<Match when={isMarkdown() && showRendered()}>
											<div class="untangled-blob-rendered overflow-auto relative">
												<MarkdownBlock markdown={text()} variant="readme" />
											</div>
										</Match>
										<Match when={blob.content !== undefined}>
											<CodeView id="blob-contents" text={text()} wrap={wrapContent()} path={path()} />
										</Match>
										<Match when={true}>
											<p class="untangled-blob-message text-center text-gray-400 dark:text-gray-500">
												Previews are not supported for this file type.
										</p>
									</Match>
								</Switch>
								</div>
							);
						})()}
				</Match>
			</Switch>
		</RepoFrame>
	);
};
