import clsx from 'clsx';
import { Bell, LogOut, RotateCcw, Save, Search, Settings } from 'lucide-solid';
import { A, useLocation, useNavigate } from '@solidjs/router';
import { createQuery, useQueryClient } from '@tanstack/solid-query';
import { For, Show, createEffect, createSignal, onCleanup, onMount, type Component, type JSX } from 'solid-js';

import { Avatar, buttonStyles, inputStyles } from './components/common';
import { clearApiCaches, resolveActor } from './lib/api/identity';
import { useAuth } from './lib/auth';
import { useLiveEvents, type LiveEvent } from './lib/live-events';
import { formatRelativeTime } from './lib/repo-utils';
import { useAppSettings } from './lib/settings';

const TOPBAR_MENU_SELECTOR = 'details[data-topbar-menu]';

const closeOpenTopbarMenus = (root: HTMLElement, target: Node) => {
	root.querySelectorAll<HTMLDetailsElement>(TOPBAR_MENU_SELECTOR).forEach((menu) => {
		if (menu.open && !menu.contains(target)) {
			menu.open = false;
		}
	});
};

const eventLabel = (event: LiveEvent) => {
	if (event.source === 'sh.tangled.repo.issue:repo') {
		return `new issue on ${event.subject}`;
	}

	if (event.source === 'sh.tangled.repo.pull:target.repo') {
		return `new pull on ${event.subject}`;
	}

	return `${event.source} -> ${event.subject}`;
};

const AppSettingsMenu: Component = () => {
	const settings = useAppSettings();
	const queryClient = useQueryClient();
	const [draftAppviewUrl, setDraftAppviewUrl] = createSignal(settings.appviewUrl());
	const [draftRouteKnotRequestsThroughAppview, setDraftRouteKnotRequestsThroughAppview] = createSignal(
		settings.routeKnotRequestsThroughAppview(),
	);
	const [error, setError] = createSignal<string | null>(null);
	const [saved, setSaved] = createSignal(false);

	createEffect(() => {
		setDraftAppviewUrl(settings.appviewUrl());
		setDraftRouteKnotRequestsThroughAppview(settings.routeKnotRequestsThroughAppview());
	});

	const reloadAppviewBackedQueries = () => {
		clearApiCaches();
		void queryClient.invalidateQueries();
	};

	const onSave = (event: SubmitEvent) => {
		event.preventDefault();
		try {
			settings.setAppviewUrl(draftAppviewUrl());
			settings.setRouteKnotRequestsThroughAppview(draftRouteKnotRequestsThroughAppview());
			setError(null);
			setSaved(true);
			reloadAppviewBackedQueries();
		} catch (cause) {
			setSaved(false);
			setError(cause instanceof Error ? cause.message : 'Invalid appview URL');
		}
	};

	const onReset = () => {
		settings.resetAppviewSettings();
		setDraftAppviewUrl(settings.defaultAppviewUrl);
		setDraftRouteKnotRequestsThroughAppview(false);
		setError(null);
		setSaved(true);
		reloadAppviewBackedQueries();
	};

	return (
		<details data-topbar-menu class="relative">
			<summary class={clsx('cursor-pointer list-none')} title="App settings">
				<Settings class="size-5 text-gray-500 dark:text-gray-400" />
			</summary>
			<div class="absolute right-0 mt-3 w-96 max-w-[calc(100vw-2rem)] rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-lg z-50">
				<form onSubmit={onSave} class="flex flex-col gap-4">
					<div class="space-y-1">
						<label for="appview-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200">
							appview URL
						</label>
						<input
							id="appview-url"
							value={draftAppviewUrl()}
							onInput={(event) => {
								setDraftAppviewUrl(event.currentTarget.value);
								setSaved(false);
								setError(null);
							}}
							placeholder={settings.defaultAppviewUrl}
							class={inputStyles()}
						/>
					</div>

					<label class="flex items-center gap-3 text-sm text-gray-700 dark:text-gray-200">
						<input
							type="checkbox"
							checked={draftRouteKnotRequestsThroughAppview()}
							onChange={(event) => {
								setDraftRouteKnotRequestsThroughAppview(event.currentTarget.checked);
								setSaved(false);
								setError(null);
							}}
							class="size-4 rounded border-gray-300 dark:border-gray-600"
						/>
						<span>route knot requests through appview</span>
					</label>

					<div class="flex items-center justify-between gap-3">
						<button type="button" class={buttonStyles()} onClick={onReset}>
							<RotateCcw class="size-4" />
							reset
						</button>
						<button type="submit" class={buttonStyles('primary')}>
							<Save class="size-4" />
							save
						</button>
					</div>

					<Show when={error()}>
						<p class="text-sm text-red-500">{error()}</p>
					</Show>
					<Show when={saved()}>
						<p class="text-sm text-gray-500 dark:text-gray-400">saved</p>
					</Show>
				</form>
			</div>
		</details>
	);
};

const LoginMenu: Component = () => {
	const auth = useAuth();
	const [identifier, setIdentifier] = createSignal('');

	const onSignIn = async (event: SubmitEvent) => {
		event.preventDefault();
		await auth.signIn(identifier());
	};

	return (
		<details data-topbar-menu class="relative">
			<summary class={clsx(buttonStyles(), 'cursor-pointer list-none')}>login</summary>
			<div class="absolute right-0 mt-3 w-80 rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-lg z-50">
				<form onSubmit={onSignIn} class="flex flex-col gap-3">
					<input
						value={identifier()}
						onInput={(event) => setIdentifier(event.currentTarget.value)}
						placeholder="handle or did"
						class={inputStyles()}
					/>
					<button type="submit" class={clsx(buttonStyles('primary'), 'justify-center')}>
						continue with OAuth
					</button>
					<Show when={auth.error()}>
						<p class="text-sm text-red-500">{auth.error()}</p>
					</Show>
				</form>
			</div>
		</details>
	);
};

const NotificationsMenu: Component = () => {
	const live = useLiveEvents();

	return (
		<details data-topbar-menu class="relative">
			<summary
				class="cursor-pointer list-none flex items-center gap-2"
				onClick={() => live.markRead()}
			>
				<div class="relative">
					<Bell class="size-5 text-gray-500 dark:text-gray-400" />
					<Show when={live.unread() > 0}>
						<span class="untangled-unread-badge absolute -top-2 -right-2 min-w-5 h-5 rounded-full bg-blue-600 text-white flex items-center justify-center px-1">
							{Math.min(live.unread(), 99)}
						</span>
					</Show>
				</div>
			</summary>
			<div class="absolute right-0 mt-3 w-80 rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg z-50 overflow-hidden">
				<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700 font-medium">live activity</div>
				<div class="max-h-96 overflow-auto">
					<Show
						when={live.events().length > 0}
						fallback={<div class="px-4 py-6 text-sm text-gray-500 dark:text-gray-400">No live events yet.</div>}
					>
						<For each={live.events()}>
							{(event) => (
								<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700 text-sm">
									<div class="font-medium break-all">{eventLabel(event)}</div>
									<div class="text-gray-500 dark:text-gray-400">{formatRelativeTime(event.receivedAt)}</div>
								</div>
							)}
						</For>
					</Show>
				</div>
			</div>
		</details>
	);
};

const UserProfileMenu: Component = () => {
	const auth = useAuth();

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

	return (
		<details data-topbar-menu class="relative">
			<summary class="cursor-pointer list-none flex items-center gap-2">
				<Avatar did={auth.currentDid()!} size="size-8 md:size-6" />
				<span class="hidden md:inline">{viewerQuery.data?.handle ?? auth.currentDid()}</span>
			</summary>
			<div class="absolute right-0 mt-4 rounded bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg z-50 text-sm w-56">
				<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
					<div class="font-medium truncate">{viewerQuery.data?.handle ?? auth.currentDid()}</div>
					<div class="text-gray-500 dark:text-gray-400 truncate">{auth.currentDid()}</div>
				</div>
				<button
					type="button"
					class="w-full px-4 py-3 flex items-center gap-2 text-left hover:bg-gray-50 hover:dark:bg-gray-700/50"
					onClick={() => void auth.signOut()}
				>
					<LogOut class="size-4" />
					logout
				</button>
			</div>
		</details>
	);
};

const TopbarSearch: Component = () => {
	const navigate = useNavigate();
	const location = useLocation();
	const [query, setQuery] = createSignal('');

	let inputRef: HTMLInputElement | undefined;
	onMount(() => {
	  const handler = (e: KeyboardEvent) => {
	  	if (e.ctrlKey || e.metaKey) return;
	    if (e.key === "/") {
	      e.preventDefault();
	      inputRef?.focus();
	    } else if (e.key === "Escape") {
	    	setQuery('');
	    	inputRef?.blur();
	    }
	  }
	  window.addEventListener("keydown", handler)
	  onCleanup(() => window.removeEventListener("keydown", handler))
	});

	createEffect(() => {
		if (location.pathname !== '/search') {
			setQuery('');
			return;
		}

		const params = new URLSearchParams(location.search);
		setQuery(params.get('q') ?? '');
	});

	const onSubmit = (event: SubmitEvent) => {
		event.preventDefault();
		const trimmed = query().trim();
		navigate(trimmed ? `/search?q=${encodeURIComponent(trimmed)}` : '/search');
	};

	return (
		<form onSubmit={onSubmit} class="relative hidden md:block">
			<div class="untangled-topbar-search-box">
				<Search class="size-4 shrink-0 text-gray-400" />
				<input
					ref={inputRef}
					value={query()}
					onInput={(event) => setQuery(event.currentTarget.value)}
					placeholder="search..."
					class="untangled-topbar-search-input"
				/>
				<kbd class="untangled-topbar-search-kbd">/</kbd>
			</div>
		</form>
	);
};

const Topbar: Component = () => {
	const auth = useAuth();
	let topbar: HTMLElement | undefined;

	onMount(() => {
		const closeMenusForOutsideClick = (event: PointerEvent) => {
			const target = event.target;
			if (!topbar || !(target instanceof Node)) {
				return;
			}

			closeOpenTopbarMenus(topbar, target);
		};

		document.addEventListener('pointerdown', closeMenusForOutsideClick);

		onCleanup(() => {
			document.removeEventListener('pointerdown', closeMenusForOutsideClick);
		});
	});

	return (
		<nav ref={(element) => { topbar = element; }} class="mx-auto space-x-4 px-6 py-2">
			<div class="flex justify-between gap-4 p-0 items-center">
				<A href="/" class="text-2xl no-underline hover:no-underline flex shrink-0 items-center gap-2">
					<img src="/static/logos/dolly.svg" alt="" class="size-8" />
					<span class="font-bold text-xl">untangled</span>
					<span class="font-normal text-xs rounded bg-gray-100 dark:bg-gray-700 px-1">alpha</span>
				</A>

				<div class="flex shrink-0 items-center gap-4">
					<TopbarSearch />
					<A href="/search" class="md:hidden text-gray-500 dark:text-gray-400" title="Search">
						<Search class="size-5" />
					</A>
					<AppSettingsMenu />
					<Show
						when={auth.currentDid()}
						fallback={<LoginMenu />}
					>
						<NotificationsMenu />
						<UserProfileMenu />
					</Show>
				</div>
			</div>
		</nav>
	);
};

export const RootShell: Component<{ children?: JSX.Element }> = (props) => (
	<div class="min-h-screen flex flex-col gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white">
		<header class="untangled-safe-header w-full shadow-sm dark:text-white bg-white dark:bg-gray-800" style="z-index: 20;">
			<Topbar />
		</header>

		<div class="flex-grow">
			<div class="untangled-shell mx-auto flex flex-col gap-4">{props.children}</div>
		</div>
	</div>
);
