import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { createFileRoute, Link } from '@tanstack/react-router'
import { Check, Filter, FilterX, LayoutGrid, List, Loader2, Search, SmilePlus, X } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'

import { Pagination } from '~/components/pagination'
import { Alert, AlertDescription, AlertTitle } from '~/components/ui/alert'
import { Button } from '~/components/ui/button'
import { Card, CardContent } from '~/components/ui/card'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '~/components/ui/dropdown-menu'
import { Input } from '~/components/ui/input'
import { Skeleton } from '~/components/ui/skeleton'

import { useDebouncedCallback } from '~/hooks/use-debounced-callback'
import { useLocalStorage } from '~/hooks/use-local-storage'
import { assetUrl } from '~/lib/directus'
import { categoriesQuery, vendorsListQuery } from '~/lib/queries'
import { cn } from '~/lib/utils'

type Search = {
  page?: number
  q?: string
  category?: string
}

export const Route = createFileRoute('/vendors/')({
  validateSearch: (search: Record<string, unknown>): Search => ({
    page: search.page ? Number(search.page) : undefined,
    q: typeof search.q === 'string' ? search.q : undefined,
    category: typeof search.category === 'string' ? search.category : undefined,
  }),
  loaderDeps: ({ search }) => search,
  loader: async ({ context: { queryClient } }) => {
    await queryClient.ensureQueryData(categoriesQuery)
  },
  component: VendorsPage,
})

type Layout = 'GRID' | 'LIST'

function truncateDescription(desc: string | null | undefined): string {
  if (!desc) return ''
  const head = desc.substring(0, 80)
  const end = head.lastIndexOf(' ')
  return desc.substring(0, end > 0 ? end : head.length) + ' …'
}

function VendorsPage() {
  const { page = 1, q = '', category = '' } = Route.useSearch()
  const navigate = Route.useNavigate()

  const { data: categories } = useSuspenseQuery(categoriesQuery)

  const [perPage, setPerPage] = useLocalStorage<number>('perPage', 12)
  const [layout, setLayout] = useLocalStorage<Layout>('layout', 'GRID')

  const [searchInput, setSearchInput] = useState(q)
  useEffect(() => {
    setSearchInput(q)
  }, [q])

  const pushSearch = useDebouncedCallback((value: string) => {
    navigate({ search: (s) => ({ ...s, q: value || undefined, page: undefined }) })
  }, 500)

  const {
    data: response,
    error,
    isFetching,
  } = useQuery(vendorsListQuery({ page, perPage, search: q, category }))

  const vendors = response?.data ?? []
  const filterCount = response?.meta?.filter_count ?? 0
  const lastPage = Math.max(1, Math.ceil(filterCount / perPage))

  useEffect(() => {
    if (response && page > lastPage) {
      navigate({ search: (s) => ({ ...s, page: lastPage }), replace: true })
    }
  }, [response, page, lastPage, navigate])

  const isEmpty = !isFetching && filterCount === 0

  return (
    <section>
      <Helmet>
        <title>Vendors</title>
      </Helmet>

      <div className="mb-5 flex flex-col items-stretch gap-4 md:flex-row md:items-center md:justify-between">
        <h1 className="text-2xl font-bold">Vendors</h1>
        <div className="flex h-9 items-center justify-center">
          {isFetching && <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />}
        </div>
        <div className="flex flex-wrap items-center gap-3">
          <div className="relative">
            <Search className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
            <Input
              className="pl-9 pr-9"
              placeholder="Search vendors"
              value={searchInput}
              onChange={(e) => {
                const v = e.target.value.toLowerCase()
                setSearchInput(v)
                pushSearch(v)
              }}
            />
            {searchInput && (
              <button
                type="button"
                aria-label="Clear search"
                onClick={() => {
                  setSearchInput('')
                  pushSearch('')
                }}
                className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
              >
                <X className="h-4 w-4" />
              </button>
            )}
          </div>

          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="outline" size="icon" aria-label="Filter by category">
                {category ? <FilterX /> : <Filter />}
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              {categories.map((cat) => (
                <DropdownMenuItem
                  key={cat.slug}
                  disabled={category === cat.slug}
                  onSelect={() =>
                    navigate({ search: (s) => ({ ...s, category: cat.slug, page: undefined }) })
                  }
                >
                  {category === cat.slug && <Check className="h-4 w-4" />}
                  {cat.name}
                </DropdownMenuItem>
              ))}
              <DropdownMenuSeparator />
              <DropdownMenuItem
                onSelect={() => navigate({ search: (s) => ({ ...s, category: undefined, page: undefined }) })}
              >
                Clear
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          <div className="inline-flex rounded-md border">
            <Button
              variant={layout === 'GRID' ? 'secondary' : 'ghost'}
              size="icon"
              className="rounded-r-none"
              onClick={() => setLayout('GRID')}
              aria-label="Grid layout"
              disabled={layout === 'GRID'}
            >
              <LayoutGrid />
            </Button>
            <Button
              variant={layout === 'LIST' ? 'secondary' : 'ghost'}
              size="icon"
              className="rounded-l-none border-l"
              onClick={() => setLayout('LIST')}
              aria-label="List layout"
              disabled={layout === 'LIST'}
            >
              <List />
            </Button>
          </div>
        </div>
      </div>

      {error && (
        <Alert variant="destructive" className="my-3">
          <AlertDescription>There was an error processing your request</AlertDescription>
        </Alert>
      )}

      {layout === 'GRID' ? (
        <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
          {isFetching && !response
            ? Array.from({ length: perPage }).map((_, k) => (
                <Card key={k}>
                  <Skeleton className="h-40 rounded-none" />
                  <CardContent className="pt-4">
                    <Skeleton className="mb-2 h-6" />
                    <Skeleton className="mb-1 h-4" />
                    <Skeleton className="h-4" />
                  </CardContent>
                </Card>
              ))
            : vendors.map((v) => (
                <Card key={v.id} className="group relative hover:border-primary/40">
                  <img
                    src={assetUrl(v.logo, 'logo-card')}
                    alt={v.name}
                    width={250}
                    height={150}
                    className="h-40 w-full rounded-t-lg bg-white object-contain"
                  />
                  <CardContent className="pt-4">
                    <Link
                      to="/vendors/$slug"
                      params={{ slug: v.slug }}
                      className="mb-2 block text-base font-extrabold after:absolute after:inset-0"
                    >
                      {v.name}
                    </Link>
                    <p className="text-sm text-muted-foreground">{truncateDescription(v.description)}</p>
                  </CardContent>
                </Card>
              ))}
        </div>
      ) : (
        <div className="overflow-hidden rounded-md border">
          <table className="w-full caption-bottom text-sm">
            <tbody>
              {isFetching && !response
                ? Array.from({ length: perPage }).map((_, k) => (
                    <tr key={k} className="border-b">
                      <td className="w-[10%] p-3">
                        <Skeleton className="h-12 w-12" />
                      </td>
                      <td className="w-[30%] p-3">
                        <Skeleton className="h-6" />
                      </td>
                      <td className="w-[60%] p-3">
                        <Skeleton className="h-6" />
                      </td>
                    </tr>
                  ))
                : vendors.map((v) => (
                    <tr key={v.id} className="border-b last:border-0 hover:bg-accent/50">
                      <td className="w-[10%] p-3 align-middle">
                        <img
                          src={assetUrl(v.logo, 'logo-card')}
                          alt={v.name}
                          className="h-12 w-12 rounded-md bg-white object-contain"
                        />
                      </td>
                      <td className="w-[30%] p-3 align-middle font-medium">
                        <Link to="/vendors/$slug" params={{ slug: v.slug }} className="hover:underline">
                          {v.name}
                        </Link>
                      </td>
                      <td className="w-[60%] p-3 align-middle text-muted-foreground">
                        {truncateDescription(v.description)}
                      </td>
                    </tr>
                  ))}
            </tbody>
          </table>
        </div>
      )}

      {isEmpty && (
        <Alert variant="info" className={cn('my-3 rounded-md py-10')}>
          <div className="flex flex-col items-center gap-2 text-center">
            <SmilePlus className="h-8 w-8" />
            <AlertTitle className="mt-4 text-lg">No results found.</AlertTitle>
            <AlertDescription className="max-w-sm">
              Try refining your search term and filters …
            </AlertDescription>
          </div>
        </Alert>
      )}

      {filterCount > perPage && (
        <>
          <Pagination
            page={page}
            itemsPerPage={perPage}
            totalItems={filterCount}
            buildSearch={(p) => ({
              q: q || undefined,
              category: category || undefined,
              page: p,
            })}
          />
          <div className="mt-5 flex items-center justify-center gap-2">
            <span className="text-sm">Results per page:</span>
            <div className="inline-flex rounded-md border">
              {[12, 24, 48].map((n, i) => (
                <Button
                  key={n}
                  variant={perPage === n ? 'secondary' : 'ghost'}
                  size="sm"
                  disabled={perPage === n}
                  onClick={() => setPerPage(n)}
                  className={cn(
                    'rounded-none',
                    i === 0 && 'rounded-l-md',
                    i === 2 && 'rounded-r-md',
                    i > 0 && 'border-l',
                  )}
                >
                  {n}
                </Button>
              ))}
            </div>
          </div>
        </>
      )}
    </section>
  )
}
