'use client';

import {
  Avatar,
  Card,
  Combobox,
  Group,
  Image,
  Input,
  Loader,
  ScrollArea,
  SegmentedControl,
  Skeleton,
  Stack,
  Text,
  TextInput,
  type TextInputProps,
  ThemeIcon,
  useCombobox,
  VisuallyHidden,
} from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { Fragment, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { searchUrls } from '../../lib/dal';
import { BiPlus } from 'react-icons/bi';
import { getDomain } from '@/lib/utils/link';
import { useMyCardsInfinite } from '@/features/cards/lib/queries/useMyCards';
import { CollectionAccessType, type UrlMetadata } from '@semble/types';
import SourceCardPreview from './SourceCardPreview';
import useSearchCollections from '@/features/collections/lib/queries/useSearchCollections';
import { getRecordKey } from '@/lib/utils/atproto';

type SearchFilter = 'cards' | 'collections';

function isValidUrl(value: string): boolean {
  try {
    new URL(value);
    return true;
  } catch {
    return false;
  }
}

function useUrlSearch(debounced: string) {
  return useQuery({
    queryKey: ['url search', debounced],
    queryFn: () =>
      searchUrls({
        searchQuery: debounced,
        limit: 10,
      }),
    enabled: debounced.trim().length > 0,
  });
}

interface Props {
  id: string;
  label: string;
  placeholder: string;
  value: string;
  error?: React.ReactNode;
  onUrlSelect: (url: string) => void;
  onUrlClear?: () => void;
  onInputChange?: (rawValue: string) => void;
  /**
   * When provided, the input is rendered as a standard Mantine `TextInput`
   * (with a visible label) and these props are spread onto it. Use this to
   * customize variant, size, leftSection, etc. When omitted, the input keeps
   * the legacy card-wrapped unstyled layout used by the connection form.
   */
  inputProps?: Omit<
    TextInputProps,
    | 'id'
    | 'label'
    | 'placeholder'
    | 'value'
    | 'onChange'
    | 'onFocus'
    | 'onBlur'
    | 'error'
    | 'required'
  > & { [K in `data-${string}`]?: unknown };
}

export default function UrlSearchInput(props: Props) {
  const combobox = useCombobox({
    onDropdownClose: () => combobox.resetSelectedOption(),
  });

  const [inputValue, setInputValue] = useState(props.value);
  const [confirmedUrl, setConfirmedUrl] = useState<string | null>(
    isValidUrl(props.value) ? props.value : null,
  );
  const [confirmedMetadata, setConfirmedMetadata] = useState<
    UrlMetadata | undefined
  >(undefined);
  const [isInputFocused, setIsInputFocused] = useState(false);
  const [debounced] = useDebouncedValue(inputValue, 200);
  const [searchFilter, setSearchFilter] = useState<SearchFilter>('cards');

  const {
    data: searchResults,
    isFetching,
    error,
  } = useUrlSearch(searchFilter === 'cards' ? debounced : '');
  const { data: recentCards, isLoading: isLoadingRecentCards } =
    useMyCardsInfinite({ limit: 5 });

  const collectionSearch = useSearchCollections({
    searchText: searchFilter === 'collections' ? debounced : '',
    limit: 5,
    enabled: searchFilter === 'collections' && debounced.trim().length > 0,
  });

  const recentCardsList = recentCards?.pages[0]?.cards ?? [];

  const urls = searchResults?.urls ?? [];
  const collections =
    collectionSearch.data?.pages.flatMap((page) => page.collections ?? []) ??
    [];
  const isCollectionSearchFetching = collectionSearch.isFetching;
  const collectionSearchError = collectionSearch.error;

  // Sync when the external value changes (e.g. after swap)
  const [prevValue, setPrevValue] = useState(props.value);
  if (props.value !== prevValue) {
    setInputValue(props.value);
    setPrevValue(props.value);
    if (isValidUrl(props.value)) {
      setConfirmedUrl(props.value);
    } else {
      setConfirmedUrl(null);
    }
    setConfirmedMetadata(undefined);
  }

  const handleClear = () => {
    setConfirmedUrl(null);
    setConfirmedMetadata(undefined);
    setInputValue('');
    props.onUrlSelect('');
    props.onUrlClear?.();
  };

  const buildCollectionUrl = (collection: {
    uri?: string;
    author: { handle: string };
  }): string | null => {
    if (!collection.uri) return null;
    const rkey = getRecordKey(collection.uri);
    if (!rkey) return null;
    const appUrl = process.env.NEXT_PUBLIC_APP_URL || window.location.origin;
    return `${appUrl}/profile/${collection.author.handle}/collections/${rkey}`;
  };

  if (confirmedUrl) {
    return (
      <Fragment>
        <SourceCardPreview sourceUrl={confirmedUrl} onRemove={handleClear} />
        <VisuallyHidden>
          <Input.Label htmlFor={props.id} required>
            {props.label}
          </Input.Label>
        </VisuallyHidden>
      </Fragment>
    );
  }

  const isSearchFetching =
    searchFilter === 'cards' ? isFetching : isCollectionSearchFetching;

  const cardOptions = urls.map((urlView) => (
    <Combobox.Option key={urlView.url} value={urlView.url} p={5}>
      <Group gap={'xs'} align="center" wrap="nowrap">
        {urlView.metadata.imageUrl && (
          <Image
            src={urlView.metadata.imageUrl}
            alt={urlView.metadata.title || 'URL thumbnail'}
            w={35}
            h={35}
            radius="sm"
            fit="cover"
          />
        )}
        <Stack gap={0}>
          <Text fw={500} c={'bright'} lineClamp={1} size="sm">
            {urlView.metadata.title || urlView.url}
          </Text>
          <Text c={'gray'} lineClamp={1} size="xs">
            {getDomain(urlView.url)}
          </Text>
        </Stack>
      </Group>
    </Combobox.Option>
  ));

  const collectionOptions = collections.map((collection) => {
    const collectionUrl = buildCollectionUrl(collection);

    if (!collectionUrl) return null;

    return (
      <Combobox.Option key={collection.id} value={collectionUrl} p={5}>
        <Group gap={'xs'} align="center" justify="space-between" wrap="nowrap">
          <Text
            fw={500}
            c={
              collection.accessType === CollectionAccessType.OPEN
                ? 'green'
                : 'bright'
            }
            lineClamp={2}
            size="sm"
          >
            {collection.name}
          </Text>

          <Avatar
            src={collection.author?.avatarUrl?.replace(
              'avatar',
              'avatar_thumbnail',
            )}
            alt={`${collection.author?.handle}'s avatar`}
            size={'sm'}
          />
        </Group>
      </Combobox.Option>
    );
  });

  const hasSearchResults =
    searchFilter === 'cards'
      ? cardOptions.length > 0
      : collectionOptions.filter(Boolean).length > 0;

  const currentError = searchFilter === 'cards' ? error : collectionSearchError;

  const isPlainLayout = !!props.inputProps;

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const val = e.currentTarget.value;
    setInputValue(val);
    props.onInputChange?.(val);
    combobox.openDropdown();
  };

  const handleFocus = () => {
    setIsInputFocused(true);
    combobox.openDropdown();
  };

  const handleBlur = () => {
    setIsInputFocused(false);
  };

  const inputElement = isPlainLayout ? (
    <TextInput
      {...props.inputProps}
      id={props.id}
      label={props.label}
      placeholder={props.placeholder}
      value={inputValue}
      onChange={handleInputChange}
      onFocus={handleFocus}
      onBlur={handleBlur}
      error={props.error}
      required
    />
  ) : (
    <Input
      id={props.id}
      component="input"
      type="text"
      py={2.5}
      placeholder={props.placeholder}
      value={inputValue}
      onChange={handleInputChange}
      onFocus={handleFocus}
      onBlur={handleBlur}
      rightSection={null}
      variant="unstyled"
      size="md"
      required
      error={props.error}
    />
  );

  const comboboxBlock = (
    <Combobox
      shadow="sm"
      radius={'md'}
      store={combobox}
      position="bottom-start"
      onOptionSubmit={(url) => {
        props.onUrlSelect(url);
        setInputValue(url);
        if (isValidUrl(url)) {
          setConfirmedUrl(url);
          // Look up metadata from search results or recent cards
          const searchMatch = urls.find((u) => u.url === url);
          const recentMatch = recentCardsList.find((c) => c.url === url);
          setConfirmedMetadata(
            searchMatch?.metadata ?? recentMatch?.cardContent,
          );
        }
        combobox.closeDropdown();
      }}
    >
      <Combobox.Target>{inputElement}</Combobox.Target>

      <Combobox.Dropdown
        hidden={
          (inputValue.trim().length === 0 &&
            debounced.trim().length === 0 &&
            !(
              isInputFocused &&
              (isLoadingRecentCards || recentCardsList.length > 0)
            )) ||
          (inputValue.trim().length === 0 && debounced.trim().length > 0)
        }
      >
        <Combobox.Options>
          <ScrollArea.Autosize
            type="scroll"
            mah={{ base: 150, xs: 300 }}
            offsetScrollbars={'present'}
          >
            {debounced.trim().length === 0 ? (
              <Fragment>
                <Text size="sm" fw={500} c="dimmed" py="xs" px={5}>
                  Recent cards
                </Text>
                {isLoadingRecentCards ? (
                  <Stack gap={5} p={5}>
                    {Array.from({ length: 5 }).map((_, i) => (
                      <Group key={i} gap="xs" align="center" wrap="nowrap">
                        <Skeleton height={35} width={35} radius="sm" />
                        <Stack gap={4} style={{ flex: 1 }}>
                          <Skeleton height={12} width="70%" radius="xl" />
                          <Skeleton height={10} width="40%" radius="xl" />
                        </Stack>
                      </Group>
                    ))}
                  </Stack>
                ) : (
                  recentCardsList.map((card) => (
                    <Combobox.Option key={card.url} value={card.url} p={5}>
                      <Group gap={'xs'} align="center" wrap="nowrap">
                        {card.cardContent.imageUrl && (
                          <Image
                            src={card.cardContent.imageUrl}
                            alt={card.cardContent.title || 'URL thumbnail'}
                            w={35}
                            h={35}
                            radius="sm"
                            fit="cover"
                          />
                        )}
                        <Stack gap={0}>
                          <Text fw={500} c={'bright'} lineClamp={1} size="sm">
                            {card.cardContent.title || card.url}
                          </Text>
                          <Text c={'gray'} lineClamp={1} size="xs">
                            {getDomain(card.url)}
                          </Text>
                        </Stack>
                      </Group>
                    </Combobox.Option>
                  ))
                )}
              </Fragment>
            ) : (
              <Fragment>
                {currentError && (
                  <Combobox.Empty>Could not search</Combobox.Empty>
                )}
                {!currentError && inputValue.trim() && (
                  <Fragment>
                    <Combobox.Option value={inputValue}>
                      <Group gap="xs" wrap="nowrap" p={0}>
                        <ThemeIcon
                          radius={'xl'}
                          size={'sm'}
                          variant="light"
                          color="gray"
                        >
                          <BiPlus />
                        </ThemeIcon>
                        <Stack gap={0} style={{ flex: 1 }}>
                          <Text size="sm" fw={600} c={'bright'}>
                            Add this link
                          </Text>
                          <Text size="xs" c="dimmed" lineClamp={1}>
                            {inputValue}
                          </Text>
                        </Stack>
                      </Group>
                    </Combobox.Option>

                    {(hasSearchResults || true) && (
                      <Fragment>
                        <Text size="sm" fw={500} c="dimmed" py="xs" px={5}>
                          Search results
                        </Text>
                        <SegmentedControl
                          value={searchFilter}
                          onChange={(value) => {
                            setSearchFilter(value as SearchFilter);
                            combobox.resetSelectedOption();
                          }}
                          data={[
                            { label: 'Cards', value: 'cards' },
                            {
                              label: 'Collections',
                              value: 'collections',
                            },
                          ]}
                          size="xs"
                          mb="xs"
                        />
                      </Fragment>
                    )}
                  </Fragment>
                )}
                {searchFilter === 'cards' && (
                  <Fragment>
                    {isFetching ? (
                      <Stack gap={5} p={5}>
                        {Array.from({ length: 3 }).map((_, i) => (
                          <Group key={i} gap="xs" align="center" wrap="nowrap">
                            <Skeleton height={35} width={35} radius="sm" />
                            <Stack gap={4} style={{ flex: 1 }}>
                              <Skeleton height={12} width="70%" radius="xl" />
                              <Skeleton height={10} width="40%" radius="xl" />
                            </Stack>
                          </Group>
                        ))}
                      </Stack>
                    ) : cardOptions.length > 0 ? (
                      <Fragment>{cardOptions}</Fragment>
                    ) : (
                      debounced.trim().length > 0 && (
                        <Combobox.Empty>No cards found</Combobox.Empty>
                      )
                    )}
                  </Fragment>
                )}
                {searchFilter === 'collections' && (
                  <Fragment>
                    {isCollectionSearchFetching ? (
                      <Stack gap={5} p={5}>
                        {Array.from({ length: 5 }).map((_, i) => (
                          <Group
                            key={i}
                            gap="xs"
                            align="center"
                            justify="space-between"
                            wrap="nowrap"
                          >
                            <Skeleton height={20} width="70%" radius="xl" />
                            <Skeleton height={26} width={26} radius="md" />
                          </Group>
                        ))}
                      </Stack>
                    ) : collectionOptions.filter(Boolean).length > 0 ? (
                      <Fragment>{collectionOptions}</Fragment>
                    ) : (
                      debounced.trim().length > 0 && (
                        <Combobox.Empty>No collections found</Combobox.Empty>
                      )
                    )}
                  </Fragment>
                )}
              </Fragment>
            )}
          </ScrollArea.Autosize>
        </Combobox.Options>
      </Combobox.Dropdown>
    </Combobox>
  );

  if (isPlainLayout) {
    return comboboxBlock;
  }

  return (
    <Card padding="xs" radius="lg" withBorder>
      <Stack gap={0}>
        {comboboxBlock}
        <VisuallyHidden>
          <Input.Label htmlFor={props.id} required>
            {props.label}
          </Input.Label>
        </VisuallyHidden>
      </Stack>
    </Card>
  );
}
