import * as React from 'react'
import {createContext, useContext, useState, useCallback} from 'react'

import {SearchIcon, X} from 'lucide-react'

import {useProductSearch} from '../../hooks/product/useProductSearch'
import {cn} from '../../lib/utils'
import {type Product} from '../../types'
import {IconButton} from '../atoms/icon-button'
import {List} from '../atoms/list'
import {Input} from '../ui/input'

import {ProductLink} from './product-link'
import {ProductLinkSkeleton} from './product-link-skeleton'

interface SearchContextValue {
  query: string
  setQuery: (query: string) => void
  products: Product[] | null
  loading: boolean
  error: Error | null
  fetchMore?: () => Promise<void>
  hasNextPage: boolean
  isTyping: boolean
}

const SearchContext = createContext<SearchContextValue | null>(null)

function useSearchContext() {
  const context = useContext(SearchContext)
  if (!context) {
    throw new Error('useSearchContext must be used within a SearchProvider')
  }
  return context
}

export interface SearchProviderProps {
  initialQuery?: string
  children: React.ReactNode
}

function SearchProvider({initialQuery = '', children}: SearchProviderProps) {
  const [query, setQueryState] = useState(initialQuery)

  const {products, loading, error, fetchMore, hasNextPage, isTyping} =
    useProductSearch({
      query,
      fetchPolicy: 'network-only',
    })

  const setQuery = useCallback((newQuery: string) => {
    setQueryState(newQuery)
  }, [])

  const contextValue: SearchContextValue = {
    query,
    setQuery,
    products,
    loading,
    error,
    fetchMore,
    hasNextPage,
    isTyping,
  }

  return (
    <SearchContext.Provider value={contextValue}>
      {children}
    </SearchContext.Provider>
  )
}

export interface SearchInputProps {
  placeholder?: string
  className?: string
  inputProps?: React.ComponentProps<'input'>
}

function SearchInput({
  placeholder = 'Search products...',
  className,
  inputProps,
}: SearchInputProps) {
  const {query, setQuery} = useSearchContext()

  const handleQueryChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setQuery(event.target.value)
      inputProps?.onChange?.(event)
    },
    [inputProps, setQuery]
  )

  return (
    <div className="relative flex flex-1 items-center rounded-full pl-4 pr-2 py-1 bg-gray-100">
      <div className="relative flex items-center">
        <SearchIcon
          size={18}
          className={cn('text-accent-foreground opacity-60')}
        />
      </div>
      <div className="relative flex-1 flex items-center mx-2">
        <Input
          name="search"
          onChange={handleQueryChange}
          placeholder={placeholder}
          type="search"
          role="searchbox"
          autoComplete="off"
          value={query}
          data-testid="search-input"
          {...inputProps}
          className={cn(
            `w-full flex overflow-hidden rounded-radius-28 border-none shadow-none py-4 px-0 text-text placeholder:text-text placeholder:opacity-60`,
            className
          )}
        />
      </div>
      <div className="relative flex items-center">
        {query === '' ? null : (
          <IconButton
            Icon={X}
            size="sm"
            filled={false}
            iconStyles=""
            onClick={() => setQuery('')}
            buttonStyles="flex items-center rounded-radius-max bg-[var(--grayscale-l20)]"
          />
        )}
      </div>
    </div>
  )
}

export interface SearchResultsListProps {
  renderItem?: (product: Product, index: number) => React.ReactNode
  height?: number
  itemHeight?: number
  initialStateComponent?: React.JSX.Element
  showScrollbar?: boolean
  overscanCount?: number
}

function SearchResultsList({
  height = window.innerHeight,
  renderItem,
  initialStateComponent,
  showScrollbar,
}: SearchResultsListProps) {
  const {query, products, loading, fetchMore, hasNextPage, isTyping} =
    useSearchContext()

  const _renderItem = (product: Product, index: number) => {
    if (renderItem) {
      return renderItem(product, index)
    }

    return (
      <div className="p-2">
        <ProductLink key={product.id} product={product} hideFavoriteAction />
      </div>
    )
  }

  const shouldShowStartingState = query.trim().length === 0
  const shouldShowLoading =
    (!products || products.length === 0) && (loading || isTyping)
  const shouldShowEmptyState = (!products || products.length === 0) && !loading

  if (shouldShowStartingState) {
    return (
      initialStateComponent || (
        <div className="flex items-center justify-center h-32 text-gray-500">
          Start typing to search for products
        </div>
      )
    )
  }

  if (shouldShowLoading) {
    return (
      <div className="flex flex-col px-4 py-4">
        <ProductLinkSkeleton className="mb-4" />
        <ProductLinkSkeleton className="mb-4" />
        <ProductLinkSkeleton className="mb-4" />
        <ProductLinkSkeleton className="mb-4" />
      </div>
    )
  }

  if (shouldShowEmptyState) {
    return (
      <div className="flex items-center justify-center h-32 text-gray-500">
        {`No products found for "${query}"`}
      </div>
    )
  }

  return (
    <List
      items={products || []}
      height={height}
      renderItem={_renderItem}
      fetchMore={hasNextPage ? fetchMore : undefined}
      showScrollbar={showScrollbar}
    />
  )
}

interface SearchProviderPropsWithoutChildren
  extends Omit<SearchProviderProps, 'children'> {}
export interface SearchResultsProps
  extends SearchProviderPropsWithoutChildren,
    SearchInputProps,
    SearchResultsListProps {
  showSearchInput?: boolean
  onProductClick?: (product: Product) => void
}

function Search({
  initialQuery,
  placeholder,
  inputProps,
  height,
  className,
  renderItem,
  itemHeight,
  onProductClick,
}: SearchResultsProps) {
  const _renderItem = (product: Product, index: number) => {
    if (renderItem) {
      return renderItem(product, index)
    }

    return (
      <div className="p-2">
        <ProductLink
          key={product.id}
          product={product}
          hideFavoriteAction
          onClick={onProductClick}
        />
      </div>
    )
  }

  return (
    <SearchProvider initialQuery={initialQuery}>
      <div className={cn('flex flex-col ', className)}>
        <div className="fixed top-0 left-0 right-0 p-4 w-full z-20 bg-background">
          <SearchInput placeholder={placeholder} inputProps={inputProps} />
        </div>
        <div className="h-14" />
        <SearchResultsList
          height={height}
          renderItem={_renderItem}
          itemHeight={itemHeight}
          showScrollbar
        />
      </div>
    </SearchProvider>
  )
}

export {SearchProvider, SearchInput, SearchResultsList, Search}
