import { A, useNavigate } from '@solidjs/router';
import { createQuery, useQueryClient } from '@tanstack/solid-query';
import { GitFork, Info, LoaderCircle } from 'lucide-solid';
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js';

import { Avatar, ErrorState, LoadingState, cardStyles } from '../../components/common';
import { resolveActor } from '../../lib/api/identity';
import { forkRepo, listRepoRecords, listUserKnots } from '../../lib/api/repos';
import { useAuth } from '../../lib/auth';
import { getErrorMessage } from '../../lib/repo-utils';
import { validateRepoName } from './new';
import { useRepoQuery } from './shared';

export const ForkRepoPage: Component = () => {
	const auth = useAuth();
	const navigate = useNavigate();
	const queryClient = useQueryClient();
	const repoQuery = useRepoQuery();

	const [name, setName] = createSignal('');
	const [description, setDescription] = createSignal('');
	const [selectedKnot, setSelectedKnot] = createSignal('');
	const [saving, setSaving] = createSignal(false);
	const [error, setError] = createSignal<string | null>(null);
	const [initializedForRepo, setInitializedForRepo] = createSignal<string | null>(null);

	const viewerQuery = createQuery(() => ({
		queryKey: ['viewer', auth.currentDid()],
		enabled: Boolean(auth.currentDid()),
		queryFn: async () => resolveActor(auth.currentDid()!),
	}));

	const knotsQuery = createQuery(() => ({
		queryKey: ['knots', auth.currentDid()],
		enabled: Boolean(auth.currentDid()),
		queryFn: async () => listUserKnots(auth.currentDid()!),
	}));

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

		setName(repo.slug);
		setDescription(repo.record.value.description ?? '');
		setInitializedForRepo(repo.repoDid);
	});

	createEffect(() => {
		const knots = knotsQuery.data;
		if (knots && knots.length === 1) {
			setSelectedKnot(knots[0]);
		}
	});

	const descriptionLength = createMemo(() => description().length);
	const viewerLabel = createMemo(() => viewerQuery.data?.handle || auth.currentDid() || '');

	const handleSubmit = async (event: SubmitEvent) => {
		event.preventDefault();

		const agent = auth.agent();
		const currentDid = auth.currentDid();
		const sourceRepo = repoQuery.data;
		if (!agent || !currentDid || !sourceRepo) return;

		const repoName = name().trim();
		const nameError = validateRepoName(repoName);
		if (nameError) {
			setError(nameError);
			return;
		}

		if (description().length > 140) {
			setError('description must be 140 characters or fewer.');
			return;
		}

		const knot = selectedKnot();
		if (!knot) {
			setError('please select a knot.');
			return;
		}

		setSaving(true);
		setError(null);

		try {
			const existingRecords = await queryClient.fetchQuery({
				queryKey: ['profile-repos', currentDid],
				queryFn: () => listRepoRecords(currentDid),
			});

			const cleanedName = repoName.endsWith('.git') ? repoName.slice(0, -4) : repoName;
			const rkey = cleanedName.toLowerCase();
			const exists = existingRecords.some(
				(record) => record.rkey.toLowerCase() === rkey || record.value.name?.trim().toLowerCase() === rkey,
			);

			if (exists) {
				setError(`you already have a repository named "${cleanedName}".`);
				setSaving(false);
				return;
			}

			const result = await forkRepo(agent, {
				sourceRepo,
				name: repoName,
				description: description(),
				knot,
			});

			await Promise.all([
				queryClient.invalidateQueries({ queryKey: ['profile-repos', currentDid] }),
				queryClient.invalidateQueries({ queryKey: ['repo-fork-count', sourceRepo.repoDid] }),
			]);

			navigate(`/${viewerLabel() || currentDid}/${result.rkey}`);
		} catch (cause) {
			setError(getErrorMessage(cause));
		} finally {
			setSaving(false);
		}
	};

	return (
		<Switch>
			<Match when={repoQuery.isLoading}>
				<div class={cardStyles('p-4')}>
					<LoadingState label="loading repository..." />
				</div>
			</Match>
			<Match when={repoQuery.error}>
				<div class={cardStyles('p-4')}>
					<ErrorState message={getErrorMessage(repoQuery.error)} />
				</div>
			</Match>
			<Match when={repoQuery.data}>
				{(repo) => (
					<Show
						when={auth.currentDid()}
						fallback={
							<div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded p-4 relative flex gap-2 items-center dark:text-white">
								please log in to fork this repository.
							</div>
						}
					>
						<div class="grid grid-cols-12 dark:text-white">
							<div class="col-span-full md:col-start-3 md:col-span-8 px-4 py-1 mb-2">
								<h1 class="text-xl font-bold dark:text-white mb-1 flex items-center gap-2 flex-wrap">
									<GitFork class="size-5 shrink-0" />
									<span>fork</span>
									<span class="flex items-center gap-1 font-normal">
										<Avatar did={repo().owner.did} size="size-5" />
										<A href={`/${repo().owner.handle}`} class="font-medium">
											{repo().owner.handle}
										</A>
										<span class="text-gray-400 dark:text-gray-500">/</span>
										<A href={`/${repo().owner.handle}/${repo().slug}`} class="font-semibold">
											{repo().slug}
										</A>
									</span>
								</h1>
							</div>

							<div class="col-span-full md:col-start-3 md:col-span-8 bg-white dark:bg-gray-800 drop-shadow-sm rounded overflow-hidden border border-gray-200 dark:border-gray-700">
								<div class="flex items-center gap-3 p-3 md:px-5 border-b border-gray-100 dark:border-gray-700 text-gray-700 dark:text-gray-300">
									<Info class="size-5 text-gray-500 dark:text-gray-400 shrink-0" />
									<p class="text-sm">
										a fork is your own copy of this repository.
										<br />
										use it to work independently, then open a pull request back upstream.
									</p>
								</div>

								<form onSubmit={handleSubmit} class="p-4 md:px-6">
									{error() && <p class="text-red-500 dark:text-red-400 text-sm mb-3">{error()}</p>}

									<div class="untangled-timeline-step untangled-timeline-step-1 untangled-fork-step">
										<div class="untangled-timeline-circle">
											<div class="untangled-timeline-circle-inner">1</div>
										</div>

										<div class="flex-1 pb-6 pt-1">
											<h2 class="untangled-fork-step-title text-lg font-medium dark:text-white">details</h2>
											<div class="text-sm text-gray-500 dark:text-gray-400 mb-3">basic repository information.</div>

											<div class="space-y-3 mt-3">
												<div>
													<label for="fork-name" class="block text-sm font-bold dark:text-white mb-1">
														REPOSITORY NAME
													</label>
													<div class="flex flex-col md:flex-row md:items-center w-full">
														<div class="shrink-0 hidden md:flex items-center px-3 py-2 gap-1.5 text-sm text-gray-700 dark:text-gray-300 border border-r-0 border-gray-300 dark:border-gray-600 rounded-l bg-gray-50 dark:bg-gray-700 h-[38px]">
															<Avatar did={auth.currentDid()!} size="size-5" />
															<span>{viewerLabel()}</span>
														</div>
														<input
															type="text"
															id="fork-name"
															name="repo_name"
															value={name()}
															onInput={(event) => setName(event.currentTarget.value)}
															required
															class="flex-1 md:rounded-r md:rounded-l-none rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 h-[38px]"
															placeholder="repository-name"
														/>
													</div>
													<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
														choose a unique, descriptive name for your repository. use letters, numbers, and hyphens.
													</p>
												</div>

												<div class="pt-2">
													<label for="fork-description" class="block text-sm font-bold dark:text-white mb-1">
														DESCRIPTION
													</label>
													<input
														type="text"
														id="fork-description"
														name="description"
														value={description()}
														onInput={(event) => setDescription(event.currentTarget.value)}
														maxlength="140"
														class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 h-[38px]"
														placeholder="a brief description of your project..."
													/>
													<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
														optional. a short description to help others understand what your project does ({descriptionLength()}/140 characters).
													</p>
												</div>
											</div>
										</div>
									</div>

									<div class="untangled-timeline-step untangled-timeline-step-2 untangled-fork-step">
										<div class="untangled-timeline-circle">
											<div class="untangled-timeline-circle-inner">2</div>
										</div>

										<div class="flex-1 pt-1">
											<h2 class="untangled-fork-step-title text-lg font-medium dark:text-white">knot selection</h2>
											<div class="text-sm text-gray-500 dark:text-gray-400 mb-3">repository settings and hosting.</div>

											<div class="space-y-3 mt-3">
												<div class="pt-2">
													<label class="block text-sm font-bold dark:text-white mb-1">
														SELECT A KNOT
													</label>
													<div class="w-full space-y-1 py-0">
														<Show
															when={!knotsQuery.isLoading}
															fallback={<div class="text-sm text-gray-500 dark:text-gray-400">loading knots...</div>}
														>
															<Show
																when={knotsQuery.data && knotsQuery.data.length > 0}
																fallback={<p class="text-sm text-gray-500 dark:text-gray-400">no knots available.</p>}
															>
																<For each={knotsQuery.data}>
																	{(knot) => (
																		<div class="flex items-center leading-tight">
																			<input
																				type="radio"
																				name="knot"
																				value={knot}
																				checked={selectedKnot() === knot}
																				onChange={() => setSelectedKnot(knot)}
																				class="mr-2 cursor-pointer"
																				id={`fork-knot-${knot}`}
																				required
																			/>
																			<label for={`fork-knot-${knot}`} class="dark:text-white lowercase cursor-pointer">
																				{knot}
																			</label>
																		</div>
																	)}
																</For>
															</Show>
														</Show>
													</div>
													<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
														a knot hosts repository data and handles git operations. you can also{' '}
														<A href="/settings/knots" class="underline">
															register your own knot
														</A>
														.
													</p>
												</div>
											</div>
										</div>
									</div>

									<div class="untangled-repo-form-submit flex justify-end">
										<button
											type="submit"
											disabled={saving() || !selectedKnot()}
											class="btn-create flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
										>
											<Show when={saving()} fallback={<GitFork class="w-4 h-4" />}>
												<LoaderCircle class="w-4 h-4 animate-spin" />
											</Show>
											fork repo
										</button>
									</div>
								</form>
							</div>
						</div>
					</Show>
				)}
			</Match>
		</Switch>
	);
};
