import clsx from 'clsx';
import {
	BookMarked,
	CircleDot,
	FileText,
	GitFork,
	GitPullRequest,
	MessageSquare,
	Search,
	Tag,
	UserRound,
	X,
	LineSquiggle,
} from 'lucide-solid';
import { A, useNavigate, useSearchParams } from '@solidjs/router';
import { createQuery } from '@tanstack/solid-query';
import type { Did, ResourceUri } from '@atcute/lexicons/syntax';
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component, type JSX } from 'solid-js';

import { ErrorState, LoadingState, PaginationControls } from '../components/common';
import { usePreloader, useRepoPreloader, useStringPreloader } from '../lib/preloading';
import { RepoStatsList } from '../components/repo';
import { getIssueRecord, getIssue } from '../lib/api/issues';
import { getPullRecord, getPull } from '../lib/api/pulls';
import { getRepoByDid } from '../lib/api/repos';
import { getStringRecord } from '../lib/api/strings';
import { issueQueryKey, pullQueryKey } from './repo/shared';
import {
	ISSUE_COLLECTION,
	STRING_COLLECTION,
} from '../lib/api/constants';
import {
	parseAtUri,
} from '../lib/api/records';
import {
	searchRecords,
	type SearchCollection,
	type SearchHit,
} from '../lib/api/search';
import { formatRelativeTime, getErrorMessage, issueHref, pullHref } from '../lib/repo-utils';

type SearchKind =
	| 'all'
	| 'repos'
	| 'issues'
	| 'pulls'
	| 'comments'
	| 'profiles'
	| 'strings'
	| 'labels';

interface SearchKindOption {
	kind: SearchKind;
	label: string;
	nsid?: SearchCollection;
}

const SEARCH_KINDS: SearchKindOption[] = [
	{ kind: 'all', label: 'all' },
	{ kind: 'repos', label: 'repos', nsid: 'sh.tangled.repo' },
	{ kind: 'issues', label: 'issues', nsid: 'sh.tangled.repo.issue' },
	{ kind: 'pulls', label: 'pulls', nsid: 'sh.tangled.repo.pull' },
	{ kind: 'comments', label: 'comments', nsid: 'sh.tangled.feed.comment' },
	{ kind: 'profiles', label: 'profiles', nsid: 'sh.tangled.actor.profile' },
	{ kind: 'strings', label: 'strings', nsid: 'sh.tangled.string' },
	{ kind: 'labels', label: 'labels', nsid: 'sh.tangled.label.definition' },
];

const SEARCH_PAGE_LIMIT = 20;
const SEARCH_OFFSET_PATTERN = /^[0-9a-fA-F]{8}$/;

const paramValue = (value: string | string[] | undefined): string => Array.isArray(value) ? value[0] ?? '' : value ?? '';

const parseSearchKind = (value: string | string[] | undefined): SearchKind => {
	const raw = paramValue(value);
	return SEARCH_KINDS.some((option) => option.kind === raw) ? raw as SearchKind : 'all';
};

const searchHref = (q: string, kind: SearchKind, cursor?: string): string => {
	const params = new URLSearchParams();
	const trimmed = q.trim();
	if (trimmed) params.set('q', trimmed);
	if (kind !== 'all') params.set('type', kind);
	if (cursor) params.set('cursor', cursor);
	const query = params.toString();
	return query ? `/search?${query}` : '/search';
};

const decodeSearchOffset = (cursor: string): number => {
	const trimmed = cursor.trim();
	if (!SEARCH_OFFSET_PATTERN.test(trimmed)) return 0;
	const parsed = Number.parseInt(trimmed, 16);
	return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
};

const normalizeSearchCursor = (cursor: string): string | undefined => {
	const trimmed = cursor.trim();
	return SEARCH_OFFSET_PATTERN.test(trimmed) ? trimmed.toLowerCase() : undefined;
};

const encodeSearchOffset = (offset: number): string | undefined => {
	if (offset <= 0) return undefined;
	return Math.max(0, offset).toString(16).padStart(8, '0');
};

const formatSearchElapsed = (milliseconds?: number): string | undefined => {
	if (milliseconds === undefined) return undefined;
	if (milliseconds < 1) return `${Math.max(1, Math.round(milliseconds * 1000))}us`;
	if (milliseconds < 10) return `${milliseconds.toFixed(2)}ms`;
	if (milliseconds < 100) return `${milliseconds.toFixed(1)}ms`;
	return `${Math.round(milliseconds)}ms`;
};

const snippet = (value?: string, max = 220): string => {
	if (typeof value !== 'string') return '';
	const normalized = value.replace(/\s+/g, ' ').trim();
	if (normalized.length <= max) {
		return normalized;
	}
	return `${normalized.slice(0, max - 1).trimEnd()}...`;
};

const SearchResultIcon: Component<{ hit: SearchHit }> = (props) => (
	<Switch fallback={<FileText class="w-4 h-4 mr-1.5 shrink-0" />}>
		<Match when={props.hit.nsid === 'sh.tangled.repo'}>
			<Show when={(props.hit.value as { source?: string }).source} fallback={<BookMarked class="w-4 h-4 mr-1.5 shrink-0" />}>
				<GitFork class="w-4 h-4 mr-1.5 shrink-0" />
			</Show>
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.repo.issue'}>
			<CircleDot class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.repo.pull'}>
			<GitPullRequest class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment' || props.hit.nsid === 'sh.tangled.feed.comment'}>
			<MessageSquare class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.actor.profile'}>
			<UserRound class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.string'}>
			<LineSquiggle class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
		<Match when={props.hit.nsid === 'sh.tangled.label.definition'}>
			<Tag class="w-4 h-4 mr-1.5 shrink-0" />
		</Match>
	</Switch>
);

const RepoContextLink: Component<{ repoDid: Did }> = (props) => {
	const repoQuery = createQuery(() => ({
		queryKey: ['repo-by-did', props.repoDid],
		queryFn: async () => getRepoByDid(props.repoDid),
		staleTime: 300_000,
	}));

	return (
		<Show
			when={repoQuery.data}
			fallback={
				<div class="flex gap-1 items-center text-sm">
					<BookMarked class="w-3 h-3" />
					<span>repo</span>
				</div>
			}
		>
			{(repo) => (
				<A
					href={`/${repo().owner.handle}/${repo().slug}`}
					class="flex min-w-0 gap-1 items-center text-sm no-underline hover:underline"
				>
					<BookMarked class="w-3 h-3 shrink-0" />
					<span class="truncate">{repo().owner.handle}/{repo().slug}</span>
				</A>
			)}
		</Show>
	);
};

const RepoThreadLink: Component<{
	kind: 'issue' | 'pull';
	repoDid: Did;
	threadRef: string;
	commentRef?: string;
	children: JSX.Element;
}> = (props) => {
	const repoQuery = createQuery(() => ({
		queryKey: ['repo-by-did', props.repoDid],
		queryFn: async () => getRepoByDid(props.repoDid),
		staleTime: 300_000,
	}));
	const preload = usePreloader();
	const href = createMemo(() => {
		const repo = repoQuery.data;
		if (!repo) return undefined;
		const fn = props.kind === 'issue' ? issueHref : pullHref;
		const baseHref = fn(repo, props.threadRef);
		return props.commentRef ? `${baseHref}#comment-${props.commentRef}` : baseHref;
	});
	const preloadProps = createMemo(() => {
		const repo = repoQuery.data;
		if (!repo) return {};
		if (props.kind === 'issue') {
			return preload(issueQueryKey(props.repoDid, props.threadRef), () => getIssue(repo, props.threadRef));
		}
		return preload(pullQueryKey(props.repoDid, props.threadRef), () => getPull(repo, props.threadRef));
	});

	return (
		<Show when={href()} fallback={<span>{props.children}</span>}>
			{(target) => <A href={target()} class="truncate min-w-0" {...preloadProps()}>{props.children}</A>}
		</Show>
	);
};

const CommentParentLink: Component<{ hit: SearchHit }> = (props) => {
	const parentInfo = createMemo(() => {
		const value = props.hit.value as any;
		let uri: ResourceUri | undefined;

		if (props.hit.nsid === 'sh.tangled.repo.issue.comment') {
			uri = value.issue;
		} else if (props.hit.nsid === 'sh.tangled.repo.pull.comment') {
			uri = value.pull;
		} else if (props.hit.nsid === 'sh.tangled.feed.comment') {
			uri = value.subject?.uri;
		}

		if (!uri) return undefined;

		try {
			const parsed = parseAtUri(uri);
			if (parsed.collection === STRING_COLLECTION) {
				return {
					uri,
					kind: 'string' as const,
				};
			}
			return {
				uri,
				kind: parsed.collection === ISSUE_COLLECTION ? 'issue' as const : 'pull' as const,
			};
		} catch {
			return undefined;
		}
	});

	const parentQuery = createQuery(() => ({
		queryKey: ['search-parent', props.hit.nsid, parentInfo()?.uri],
		enabled: Boolean(parentInfo()?.uri),
		queryFn: async () => {
			const info = parentInfo()!;
			if (info.kind === 'string') {
				return { kind: 'string' as const, data: await getStringRecord(info.uri) };
			}
			return info.kind === 'issue'
				? { kind: 'issue' as const, record: await getIssueRecord(info.uri) }
				: { kind: 'pull' as const, record: await getPullRecord(info.uri) };
		},
		staleTime: 300_000,
	}));

	return (
		<Show when={parentQuery.data} fallback={<span>comment</span>}>
			{(parent) => (
				<Switch>
					<Match when={parent().kind === 'string'}>
						<A
							href={`/strings/${(parent() as any).data.owner.handle || (parent() as any).data.owner.did}/${(parent() as any).data.record.rkey}#comment-${props.hit.rkey}`}
							class="truncate min-w-0 font-medium"
						>
							comment on string
						</A>
					</Match>
					<Match when={parent().kind !== 'string'}>
						<RepoThreadLink
							kind={(parent() as any).kind}
							repoDid={(parent() as any).kind === 'issue'
								? ((parent() as any).record.value as { repo: Did }).repo
								: ((parent() as any).record.value as { target: { repo: Did } }).target.repo
							}
							threadRef={(parent() as any).record.rkey}
							commentRef={props.hit.rkey}
						>
							comment on {(parent() as any).kind}
						</RepoThreadLink>
					</Match>
				</Switch>
			)}
		</Show>
	);
};

const ResultRepoContext: Component<{ hit: SearchHit }> = (props) => {
	const directRepoDid = createMemo(() => {
		if (props.hit.nsid === 'sh.tangled.repo.issue') {
			return (props.hit.value as { repo: Did }).repo;
		}
		if (props.hit.nsid === 'sh.tangled.repo.pull') {
			return (props.hit.value as { target: { repo: Did } }).target.repo;
		}
		return undefined;
	});
	const isRepoComment = createMemo(() =>
		props.hit.nsid === 'sh.tangled.repo.issue.comment' ||
		props.hit.nsid === 'sh.tangled.repo.pull.comment' ||
		props.hit.nsid === 'sh.tangled.feed.comment'
	);
	const parentUri = createMemo(() => {
		if (!isRepoComment()) return undefined;
		const value = props.hit.value as any;
		if (props.hit.nsid === 'sh.tangled.repo.issue.comment') {
			return value.issue;
		}
		if (props.hit.nsid === 'sh.tangled.repo.pull.comment') {
			return value.pull;
		}
		if (props.hit.nsid === 'sh.tangled.feed.comment') {
			return value.subject?.uri;
		}
		return undefined;
	});
	const parentRepoDidQuery = createQuery(() => ({
		queryKey: ['search-parent-repo', parentUri()],
		enabled: Boolean(parentUri()),
		queryFn: async () => {
			const uri = parentUri()!;
			const parsed = parseAtUri(uri);
			if (parsed.collection === STRING_COLLECTION) {
				return undefined;
			}
			if (parsed.collection === ISSUE_COLLECTION) {
				const issue = await getIssueRecord(uri);
				return (issue.value as { repo: Did }).repo;
			}
			const pull = await getPullRecord(uri);
			return (pull.value as { target: { repo: Did } }).target.repo;
		},
		staleTime: 300_000,
	}));

	return (
		<Show when={directRepoDid() || parentRepoDidQuery.data}>
			{(did) => <RepoContextLink repoDid={did()} />}
		</Show>
	);
};

const ResultTitle: Component<{ hit: SearchHit }> = (props) => {
	const preloadRepo = useRepoPreloader();
	const preloadString = useStringPreloader();

	const title = createMemo(() => {
		const value = props.hit.value as {
			title?: string;
			name?: string;
			filename?: string;
			preferredHandle?: string;
		};
		return value.title || value.name || value.filename || value.preferredHandle || props.hit.rkey;
	});
	const repoName = createMemo(() => {
		const name = (props.hit.value as { name?: string }).name;
		return (typeof name === 'string' ? name.trim() : null) || props.hit.rkey;
	});

	return (
		<div class="flex items-center min-w-0 flex-1 mr-2">
			<SearchResultIcon hit={props.hit} />
			<Switch fallback={<span class="truncate min-w-0">{title()}</span>}>
					<Match when={props.hit.nsid === 'sh.tangled.repo'}>
						<A
							href={`/${props.hit.author.handle}/${repoName()}`}
							class="truncate min-w-0"
							{...preloadRepo(props.hit.author.handle, repoName())}
						>
							{props.hit.author.handle}/{repoName()}
						</A>
					</Match>
					<Match when={props.hit.nsid === 'sh.tangled.repo.issue'}>
						<RepoThreadLink kind="issue" repoDid={(props.hit.value as { repo: Did }).repo} threadRef={props.hit.rkey}>
							{title()}
						</RepoThreadLink>
					</Match>
					<Match when={props.hit.nsid === 'sh.tangled.repo.pull'}>
						<RepoThreadLink kind="pull" repoDid={(props.hit.value as { target: { repo: Did } }).target.repo} threadRef={props.hit.rkey}>
							{title()}
						</RepoThreadLink>
					</Match>
					<Match when={props.hit.nsid === 'sh.tangled.actor.profile'}>
						<A href={`/${props.hit.author.handle}`} class="truncate min-w-0">
							{props.hit.author.handle}
						</A>
					</Match>
					<Match when={props.hit.nsid === 'sh.tangled.string'}>
						<A
							href={`/strings/${props.hit.author.handle || props.hit.author.did}/${props.hit.rkey}`}
							class="truncate min-w-0"
							{...preloadString(props.hit.author.handle || props.hit.author.did, props.hit.rkey)}
						>
							{props.hit.author.handle || props.hit.author.did}/{title()}
						</A>
					</Match>
				<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment' || props.hit.nsid === 'sh.tangled.feed.comment'}>
					<CommentParentLink hit={props.hit} />
				</Match>
			</Switch>
		</div>
	);
};

const SearchResultCard: Component<{ hit: SearchHit }> = (props) => {
	const createdAt = createMemo(() => (props.hit.value as { createdAt?: string }).createdAt);
	const body = createMemo(() => {
		const value = props.hit.value as any;
		const text = typeof value.body === 'string'
			? value.body
			: (value.body?.text ?? value.description ?? value.contents ?? [value.location, value.pronouns].filter(Boolean).join(' '));
		return snippet(text);
	});
	const topics = createMemo(() => props.hit.nsid === 'sh.tangled.repo'
		? (props.hit.value as { topics?: string[] }).topics ?? []
		: []);

	return (
		<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
			<div class="min-h-32 py-4 px-6 gap-1 flex flex-col drop-shadow-sm bg-white dark:bg-gray-800">
				<div class="font-medium dark:text-white flex items-center justify-between">
					<ResultTitle hit={props.hit} />
				</div>

				<Show when={body()}>
					<div class="untangled-search-description text-gray-600 dark:text-gray-300 text-sm">{body()}</div>
				</Show>

				<div class="text-gray-400 text-sm font-mono inline-flex flex-wrap gap-4 mt-auto pt-4">
					<div class="flex gap-1 items-center text-sm">
						<UserRound class="w-3 h-3" />
						<a href={`/${props.hit.author.did}`}>{props.hit.author.handle}</a>
					</div>
					<ResultRepoContext hit={props.hit} />
					<Show when={createdAt()}>
						<div class="flex gap-1 items-center text-sm">
							<span>{formatRelativeTime(createdAt())}</span>
						</div>
					</Show>
					<Show when={props.hit.nsid === 'sh.tangled.repo' && (props.hit.value as { repoDid?: string }).repoDid}>
						{(repoDid) => <RepoStatsList repoDid={repoDid() as Did} />}
					</Show>
				</div>

				<Show when={topics().length > 0}>
					<div class="mt-2 flex flex-wrap gap-2">
						<For each={topics()}>
							{(topic) => (
								<span class="rounded border border-gray-200 px-2 py-0.5 text-xs text-gray-500 dark:border-gray-700 dark:text-gray-400">
									{topic}
								</span>
							)}
						</For>
					</div>
				</Show>
			</div>
		</div>
	);
};

const TypeFilters: Component<{ query: string; active: SearchKind }> = (props) => (
	<div class="flex gap-2 md:flex-wrap overflow-x-auto pb-2 md:pb-0">
		<For each={SEARCH_KINDS}>
			{(option) => (
				<A
					href={searchHref(props.query, option.kind)}
					class={clsx(
						'w-fit flex items-center flex-shrink-0 gap-2 font-normal normal-case rounded py-1 px-2 border text-sm transition-colors no-underline hover:no-underline',
						option.kind === props.active
							? 'border-gray-400 bg-gray-100 text-gray-900 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-100'
							: 'border-gray-200 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700',
					)}
				>
					<span class="untangled-search-type-dot" />
					<span>{option.label}</span>
				</A>
			)}
		</For>
	</div>
);

const SearchStatistics: Component<{
	returnedCount?: number;
	returnedStart?: number;
	elapsedMs?: number;
	hasMore?: boolean;
}> = (props) => {
	const returnedThrough = createMemo(() => (props.returnedStart ?? 0) + (props.returnedCount ?? 0));
	const elapsed = createMemo(() => formatSearchElapsed(props.elapsedMs));

	return (
		<Show when={props.returnedCount !== undefined}>
			<div class="text-xs text-gray-600 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700 py-2">
				returned {returnedThrough()}{props.hasMore ? '+' : ''} indexed record{returnedThrough() === 1 ? '' : 's'}
				<Show when={elapsed()}> in {elapsed()}</Show>
			</div>
		</Show>
	);
};

const SearchOptionsPanel: Component<{
	query: string;
	kind: SearchKind;
	returnedCount?: number;
	returnedStart?: number;
	elapsedMs?: number;
	hasMore?: boolean;
}> = (props) => (
	<div class="flex flex-col gap-6 px-2 md:px-0">
		<div>
			<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">sort by</h3>
			<select class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 dark:text-white text-sm">
				<option>relevance</option>
			</select>
		</div>

		<div>
			<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">content</h3>
			<TypeFilters query={props.query} active={props.kind} />
			<p class="hidden md:block text-gray-500 dark:text-gray-400 text-xs mt-3">
				filter by indexed record type.
			</p>
		</div>

		<SearchStatistics
			returnedCount={props.returnedCount}
			returnedStart={props.returnedStart}
			elapsedMs={props.elapsedMs}
			hasMore={props.hasMore}
		/>
	</div>
);

export const SearchPage: Component = () => {
	const [searchParams] = useSearchParams();
	const navigate = useNavigate();
	const q = createMemo(() => paramValue(searchParams.q));
	const kind = createMemo(() => parseSearchKind(searchParams.type));
	const cursor = createMemo(() => paramValue(searchParams.cursor));
	const normalizedCursor = createMemo(() => normalizeSearchCursor(cursor()));
	const currentOffset = createMemo(() => decodeSearchOffset(normalizedCursor() ?? ''));
	const selectedKind = createMemo(() => SEARCH_KINDS.find((option) => option.kind === kind()) ?? SEARCH_KINDS[0]);
	const [draft, setDraft] = createSignal(q());

	createEffect(() => {
		setDraft(q());
	});

	const searchQuery = createQuery(() => ({
		queryKey: ['global-search', q(), kind(), normalizedCursor()],
		enabled: Boolean(q().trim()),
		queryFn: async () => {
			const startedAt = performance.now();
			const result = await searchRecords({
				q: q(),
				nsid: selectedKind().nsid,
				cursor: normalizedCursor(),
				limit: SEARCH_PAGE_LIMIT,
			});
			return { ...result, elapsedMs: performance.now() - startedAt };
		},
	}));

	const onSubmit = (event: SubmitEvent) => {
		event.preventDefault();
		navigate(searchHref(draft(), kind()));
	};

	const clearHref = createMemo(() => searchHref('', kind()));
	const hits = createMemo(() => searchQuery.data?.hits ?? []);
	const returnedCount = createMemo(() => searchQuery.data ? hits().length : undefined);

	return (
		<>
			<h1 class="text-2xl font-bold mb-1 px-2">search</h1>

			<div class="untangled-search-grid px-2">
				<div class="untangled-search-main">
					<div class="sticky top-0 z-10 bg-slate-100 dark:bg-gray-900">
						<form onSubmit={onSubmit} class="py-1.5 flex items-center gap-2">
							<div class="flex relative w-full">
								<div class="flex-1 flex relative">
									<input
										id="search-q"
										value={draft()}
										onInput={(event) => setDraft(event.currentTarget.value)}
										placeholder="search..."
										class="untangled-search-page-input peer"
									/>
									<A
										href={clearHref()}
										class={clsx(
											'absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300',
											!draft().trim() && 'hidden',
										)}
									>
										<X class="w-4 h-4" />
									</A>
								</div>
								<button type="submit" class="untangled-search-page-submit">
									<Search class="w-4 h-4" />
								</button>
							</div>
						</form>

						<div class="md:hidden pb-1.5">
							<TypeFilters query={q()} active={kind()} />
						</div>
					</div>

					<Show
						when={q().trim()}
						fallback={<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">enter a query to search indexed records.</div>}
					>
						<Switch>
							<Match when={searchQuery.isLoading}>
								<div class="p-12 border border-gray-200 dark:border-gray-700 rounded">
									<LoadingState label="searching..." />
								</div>
							</Match>
							<Match when={searchQuery.error}>
								<div class="p-6 border border-gray-200 dark:border-gray-700 rounded">
									<ErrorState message={getErrorMessage(searchQuery.error)} />
								</div>
							</Match>
							<Match when={searchQuery.data}>
								{(data) => (
									<>
										<div id="search-results" class="grid grid-cols-1 gap-4 mb-6">
											<Show
												when={data().hits.length > 0}
												fallback={
													<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
														<span>no results found.</span>
													</div>
												}
											>
												<For each={data().hits}>
													{(hit) => <SearchResultCard hit={hit} />}
												</For>
											</Show>
										</div>

										<div class="md:hidden mb-4">
											<SearchStatistics
												returnedCount={data().hits.length}
												returnedStart={currentOffset()}
												elapsedMs={data().elapsedMs}
												hasMore={Boolean(data().cursor)}
											/>
										</div>

										<PaginationControls
											offset={currentOffset()}
											limit={SEARCH_PAGE_LIMIT}
											hasNext={Boolean(data().cursor)}
											hrefForOffset={(offset) => searchHref(q(), kind(), encodeSearchOffset(offset))}
										/>
									</>
								)}
							</Match>
						</Switch>
					</Show>
				</div>

				<div class="untangled-search-sidebar">
					<SearchOptionsPanel
						query={q()}
						kind={kind()}
						returnedCount={returnedCount()}
						returnedStart={currentOffset()}
						elapsedMs={searchQuery.data?.elapsedMs}
						hasMore={Boolean(searchQuery.data?.cursor)}
					/>
				</div>
			</div>
		</>
	);
};
