import clsx from 'clsx';
import {
	Ban,
	ChevronLeft,
	ChevronRight,
	CircleDot,
	GitMerge,
	GitPullRequest,
	GitPullRequestClosed,
	LoaderCircle,
	UserRound,
} from 'lucide-solid';
import { A } from '@solidjs/router';
import { Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js';
import { createQuery, useQueryClient } from '@tanstack/solid-query';
export { useQueryClient };
import { resolveAvatarUrl } from '../lib/api/identity';

export const ToggleButton: Component<{
	active: boolean;
	label: string;
	meta?: string;
	onClick: () => void;
}> = (props) => (
	<button
		type="button"
		class={clsx(
			'untangled-toggle-button inline-flex items-center gap-2 border-r border-gray-300 px-3 text-sm transition-colors last:border-r-0 dark:border-gray-700',
			props.active ? 'untangled-toggle-button-active' : 'untangled-toggle-button-idle',
		)}
		onClick={props.onClick}
	>
		<span>{props.label}</span>
		<Show when={props.meta}>
			<span
				class={clsx(
					'untangled-toggle-button-meta text-xs',
					props.active ? 'untangled-toggle-button-meta-active' : 'untangled-toggle-button-meta-idle',
				)}
			>
				{props.meta}
			</span>
		</Show>
	</button>
);

export const PaginationControls: Component<{
	basePath?: string;
	offset: number;
	limit: number;
	totalCount?: number;
	hasNext?: boolean;
	query?: Record<string, string | undefined>;
	hrefForOffset?: (offset: number) => string;
}> = (props) => {
	const safeLimit = createMemo(() => Math.max(1, props.limit));
	const hasExactTotal = createMemo(() => typeof props.totalCount === 'number');
	const lastOffset = createMemo(() => Math.floor(Math.max((props.totalCount ?? 0) - 1, 0) / safeLimit()) * safeLimit());
	const previousOffset = createMemo(() => Math.max(0, props.offset - safeLimit()));
	const nextOffset = createMemo(() => props.offset + safeLimit());
	const hasNextPage = createMemo(() =>
		hasExactTotal() ? nextOffset() < (props.totalCount ?? 0) : Boolean(props.hasNext),
	);
	const pageNumber = (offset: number) => Math.floor(offset / safeLimit()) + 1;
	const hrefForOffset = (offset: number) => {
		if (props.hrefForOffset) {
			return props.hrefForOffset(Math.max(0, offset));
		}

		const params = new URLSearchParams();

		for (const [key, value] of Object.entries(props.query ?? {})) {
			if (value) {
				params.set(key, value);
			}
		}

		params.set('offset', String(Math.max(0, offset)));
		params.set('limit', String(safeLimit()));
		return `${props.basePath ?? ''}?${params.toString()}`;
	};
	const showNextNumber = createMemo(() => hasExactTotal() ? nextOffset() < lastOffset() : hasNextPage());
	const showGap = createMemo(() => hasExactTotal() && nextOffset() < (props.totalCount ?? 0) - safeLimit() * 2);

	const linkClass = 'flex items-center gap-1 no-underline hover:no-underline dark:text-white text-sm';

	return (
		<Show when={props.offset > 0 || hasNextPage()}>
			<div class="mt-4 flex items-center justify-center gap-5">
				<Show
					when={props.offset > 0}
					fallback={
						<span class={clsx(linkClass, 'cursor-not-allowed opacity-50')}>
							<ChevronLeft class="size-4" />
							prev
						</span>
					}
				>
					<A href={hrefForOffset(previousOffset())} class={linkClass}>
						<ChevronLeft class="size-4" />
						prev
					</A>
				</Show>

				<Show when={props.offset > 0}>
					<A href={hrefForOffset(0)}>1</A>
				</Show>
				<Show when={hasExactTotal() && previousOffset() > 0}>
					<A href={hrefForOffset(previousOffset())}>{pageNumber(previousOffset())}</A>
				</Show>

				<span class="rounded-sm border border-gray-200 bg-white px-2 py-1 dark:border-gray-700 dark:bg-gray-800">
					{pageNumber(props.offset)}
				</span>

				<Show when={showNextNumber()}>
					<A href={hrefForOffset(nextOffset())}>{pageNumber(nextOffset())}</A>
				</Show>
				<Show when={showGap()}>
					<span class="text-gray-400 dark:text-gray-500">--</span>
				</Show>
				<Show when={hasExactTotal() && props.offset < lastOffset()}>
					<A href={hrefForOffset(lastOffset())}>{pageNumber(lastOffset())}</A>
				</Show>

				<Show
					when={hasNextPage()}
					fallback={
						<span class={clsx(linkClass, 'cursor-not-allowed opacity-50')}>
							next
							<ChevronRight class="size-4" />
						</span>
					}
				>
					<A href={hrefForOffset(nextOffset())} class={linkClass}>
						next
						<ChevronRight class="size-4" />
					</A>
				</Show>
			</div>
		</Show>
	);
};

export const LoadingState: Component<{ label: string }> = (props) => (
	<div class="flex items-center justify-center gap-2 text-gray-500 dark:text-gray-400">
		<LoaderCircle class="size-4 animate-spin" />
		<span>{props.label}</span>
	</div>
);

export const SkeletonBlock: Component<{ class?: string }> = (props) => (
	<div aria-hidden="true" class={clsx('untangled-skeleton rounded', props.class)} />
);

export const ErrorState: Component<{ message: string }> = (props) => (
	<div class="text-red-500 dark:text-red-300">{props.message}</div>
);

export const PlaceholderAvatar: Component<{ size?: string; iconSize?: string }> = (props) => (
	<div
		class={clsx(
			'rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center flex-shrink-0',
			props.size ?? 'size-8',
		)}
	>
		<UserRound class={clsx('text-gray-400 dark:text-gray-500', props.iconSize ?? 'size-5')} />
	</div>
);

export const Avatar: Component<{ did: string; size?: string }> = (props) => {
	const [failed, setFailed] = createSignal(false);
	const avatarQuery = createQuery(() => ({
		queryKey: ['avatar', props.did],
		queryFn: async () => resolveAvatarUrl(props.did),
		staleTime: 300_000,
	}));

	createEffect(() => {
		props.did;
		setFailed(false);
	});

	return (
		<Show
			when={avatarQuery.data && !failed()}
			fallback={<PlaceholderAvatar size={props.size} />}
		>
			<img
				src={avatarQuery.data!}
				alt=""
				class={clsx(
					'rounded-full border border-gray-300 dark:border-gray-700 bg-gray-200 dark:bg-gray-700 object-cover',
					props.size ?? 'size-8',
				)}
				onError={() => setFailed(true)}
			/>
		</Show>
	);
};

export const StateBadge: Component<{
	state: 'open' | 'closed' | 'merged';
	kind: 'issue' | 'pull';
}> = (props) => {
	const isIssue = () => props.kind === 'issue';
	const classes = createMemo(() => {
		if (props.state === 'open') {
			return 'bg-green-600 dark:bg-green-700';
		}
		if (props.state === 'merged') {
			return 'bg-purple-600 dark:bg-purple-700';
		}
		if (props.kind === 'pull') {
			return 'bg-red-600 dark:bg-red-700';
		}
		return 'bg-gray-800 dark:bg-gray-700';
	});

	return (
		<span class={clsx('inline-flex items-center gap-1.5 rounded px-2 py-1 text-sm font-medium text-white', classes())}>
			<Switch>
				<Match when={props.state === 'open'}>
					{isIssue() ? <CircleDot class="size-3.5" /> : <GitPullRequest class="size-3.5" />}
				</Match>
				<Match when={props.state === 'merged'}>
					<GitMerge class="size-3.5" />
				</Match>
				<Match when={props.kind === 'pull'}>
					<GitPullRequestClosed class="size-3.5" />
				</Match>
				<Match when={true}>
					<Ban class="size-3.5" />
				</Match>
			</Switch>
			{props.state}
		</span>
	);
};

export const cardStyles = (extra?: string): string =>
	clsx('bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700', extra);

export const buttonStyles = (variant: 'default' | 'primary' = 'default'): string =>
	clsx(
		'untangled-button inline-flex items-center gap-2 rounded border px-3 text-sm no-underline hover:no-underline',
		variant === 'primary'
			? 'btn-create text-white hover:text-white'
			: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/40',
	);

export const inputStyles = (): string =>
	'untangled-input 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';

export const textareaStyles = (): string =>
	'untangled-textarea 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';
