<script lang="ts">
	import { resolve } from "$app/paths";
	import { page } from "$app/state";
	import { createAppviewClient } from "$lib/api/appview";
	import { errorMessage } from "$lib/api/load";
	import { searchCode } from "$lib/api/search";
	import { getAuth } from "$lib/auth.svelte";
	import { loginWithReturn } from "$lib/auth/guards";
	import EmptyState from "$lib/components/ui/EmptyState.svelte";
	import ErrorBox from "$lib/components/ui/Error.svelte";
	import Spinner from "$lib/components/ui/Spinner.svelte";
	import CodeResultCard from "../CodeResultCard.svelte";
	import SearchBar from "../SearchBar.svelte";
	import SearchSidebar from "../SearchSidebar.svelte";
	import { searchUrl } from "../SearchTabs.svelte";
	import { matchCount } from "../chunks";
	import type { CodeResult } from "../types";

	const PAGE_LIMIT = 50;

	interface Props {
		/** the committed query from the url, not the search box's draft value */
		query: string;
	}

	let { query }: Props = $props();

	const auth = getAuth();

	let results = $state<CodeResult[]>([]);
	let requesting = $state(false);
	let error = $state<string | null>(null);

	const apiUrl = $derived(page.data.publicConfig?.apiUrl as string | undefined);

	const signedIn = $derived(auth.currentDid !== null);
	const waitingForAgent = $derived(signedIn && auth.agent === null);
	const loading = $derived(requesting || (waitingForAgent && query.trim() !== ""));

	// fetch results on page load
	$effect(() => {
		const q = query.trim();
		const agent = auth.agent;
		const service = apiUrl;

		// no agent is idle, not a failure: the request simply waits for one
		if (!q || !agent || !service) {
			results = [];
			requesting = false;
			error = null;
			return;
		}

		const controller = new AbortController();
		const { signal } = controller;
		requesting = true;
		error = null;

		searchCode(
			createAppviewClient({ apiUrl: service, agent }),
			{ q, limit: PAGE_LIMIT },
			{ signal }
		)
			.then((found) => {
				if (signal.aborted) return;
				results = found.results;
			})
			.catch((cause: unknown) => {
				if (signal.aborted) return;
				results = [];
				error = errorMessage(cause, "Could not search code.");
			})
			.finally(() => {
				if (signal.aborted) return;
				requesting = false;
			});

		return () => controller.abort();
	});

	let draft = $derived(query);
	const loginHref = $derived(resolve(loginWithReturn(searchUrl("code", query)) as "/login"));
	const matches = $derived(results.reduce((n, r) => n + matchCount(r.chunks), 0));
</script>

<svelte:head>
	<title>Code Search &middot; Tangled</title>
</svelte:head>

<div class="mx-auto flex w-full max-w-screen-lg flex-col gap-4 px-4 py-8">
	<h1 class="mb-4 px-2 text-2xl font-semibold">Code Search</h1>
	<div class="grid grid-cols-1 gap-4 px-2 md:grid-cols-4">
		<div class="col-span-1 space-y-4 md:col-span-3">
			<SearchBar type="code" bind:query={draft} placeholder="Search code..." mono />

			{#if !signedIn}
				<EmptyState message="Code search needs an account.">
					<a href={loginHref} class="underline">Login</a>
				</EmptyState>
			{:else if !query}
				<EmptyState message="Enter a query to search code." />
			{:else if loading}
				<div class="flex justify-center p-12 text-foreground-subtle">
					<Spinner class="size-6" />
				</div>
			{:else if error}
				<ErrorBox label={error} />
			{:else}
				<div class="grid grid-cols-1 gap-4">
					{#each results as result (`${result.ownerHandle}/${result.repoName}/${result.path}`)}
						<CodeResultCard {result} />
					{:else}
						<EmptyState message="No results found." />
					{/each}
				</div>
			{/if}
		</div>

		<SearchSidebar bind:query={draft} active="code">
			{#if matches > 0}
				<div class="border-t border-border-default py-2 text-xs text-foreground-muted">
					Found {matches}
					{matches === 1 ? "match" : "matches"} in {results.length}
					{results.length === 1 ? "file" : "files"}
				</div>
			{/if}
		</SearchSidebar>
	</div>
</div>
