import { Search, SearchX, SlidersHorizontal } from 'lucide-react'
import { useState, type FormEvent } from 'react'
import { useSearchParams } from 'react-router-dom'

import { AppShell } from '@/components/layout/AppShell'
import { Alert, Button, Card, EmptyState, PageSpinner } from '@/components/ui'
import { DomainCard } from '@/features/marketplace/DomainCard'
import { useCategories, useDomainSearch, useTlds } from '@/features/marketplace/queries'
import type { DomainSearchFilters } from '@/features/marketplace/queries'

/**
 * Domain search and browse.
 *
 * Filter state lives in the URL rather than in component state, because these
 * are the URLs people share and that search engines index: `/domains?tld=io`
 * has to be a page, not a state a visitor has to reproduce by clicking. It also
 * means the back button steps through filter changes, which is what a browser's
 * back button is for.
 */
const SORT_OPTIONS = [
  { value: 'relevance', label: 'Best match' },
  { value: 'recent', label: 'Recently listed' },
  { value: 'price_asc', label: 'Price: low to high' },
  { value: 'price_desc', label: 'Price: high to low' },
  { value: 'length_asc', label: 'Shortest first' },
  { value: 'length_desc', label: 'Longest first' },
] as const

const PER_PAGE = 24

export function DomainsPage() {
  const [params, setParams] = useSearchParams()
  const [showFilters, setShowFilters] = useState(false)

  // The search box is the one control that is not applied on every keystroke,
  // so it keeps local state until submitted.
  const [term, setTerm] = useState(params.get('q') ?? '')

  const page = Number(params.get('page') ?? '1')

  const filters: DomainSearchFilters = {
    per_page: PER_PAGE,
    ...(params.get('q') ? { q: params.get('q') as string } : {}),
    ...(params.get('tld') ? { tld: params.get('tld') as string } : {}),
    ...(params.get('category') ? { category: params.get('category') as string } : {}),
    ...(params.get('sort') ? { sort: params.get('sort') as string } : {}),
    ...(params.get('max_price') ? { max_price: Number(params.get('max_price')) } : {}),
    ...(params.get('max_length') ? { max_length: Number(params.get('max_length')) } : {}),
    ...(params.get('exclude_hyphens') === '1' ? { exclude_hyphens: true } : {}),
    ...(params.get('exclude_digits') === '1' ? { exclude_digits: true } : {}),
    ...(params.get('premium') === '1' ? { premium_tld: true } : {}),
    ...(page > 1 ? { page } : {}),
  }

  const { data, isLoading, isError, error, isPlaceholderData } = useDomainSearch(filters)
  const { data: tlds } = useTlds()
  const { data: categories } = useCategories()

  /**
   * Writes one filter into the URL.
   *
   * Always resets to page one. Keeping the page number across a filter change
   * is how someone lands on an empty page 4 of a 2-page result set and
   * concludes the marketplace has nothing.
   */
  const setFilter = (key: string, value: string | null) => {
    const next = new URLSearchParams(params)

    if (value === null || value === '') {
      next.delete(key)
    } else {
      next.set(key, value)
    }

    next.delete('page')
    setParams(next)
  }

  const goToPage = (target: number) => {
    const next = new URLSearchParams(params)
    next.set('page', String(target))
    setParams(next)
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }

  const submitSearch = (event: FormEvent) => {
    event.preventDefault()
    setFilter('q', term.trim())
  }

  const clearAll = () => {
    setTerm('')
    setParams(new URLSearchParams())
  }

  const activeFilterCount = [
    'tld',
    'category',
    'max_price',
    'max_length',
    'exclude_hyphens',
    'exclude_digits',
    'premium',
  ].filter((key) => params.get(key) !== null).length

  const listings = data?.data ?? []
  const meta = data?.meta

  return (
    <AppShell>
      <div className="container-page py-10 lg:py-14">
        <div className="max-w-2xl">
          <h1 className="text-2xl font-semibold tracking-[-0.02em] text-text-primary sm:text-3xl">
            Browse domains
          </h1>
          <p className="mt-2 text-text-secondary">
            Every name here has had its ownership proved before being listed.
          </p>
        </div>

        {/* -------------------------------------------------------------- */}
        {/* Search and sort                                                */}
        {/* -------------------------------------------------------------- */}
        <form onSubmit={submitSearch} className="mt-8 flex flex-col gap-2.5 sm:flex-row" role="search">
          <label htmlFor="domain-search" className="sr-only">
            Search for a domain name
          </label>

          <div className="relative flex-1">
            <Search
              aria-hidden
              className="pointer-events-none absolute top-1/2 left-4 size-5 -translate-y-1/2 text-text-muted"
            />
            <input
              id="domain-search"
              type="search"
              value={term}
              onChange={(event) => setTerm(event.target.value)}
              placeholder="Search names, or paste a domain…"
              autoComplete="off"
              className="h-12 w-full rounded-lg border border-border-control bg-surface pr-4 pl-12 text-base text-text-primary shadow-xs transition-colors duration-150 placeholder:text-text-muted hover:border-border-strong focus:border-border-accent focus:outline-none"
            />
          </div>

          <Button type="submit" size="lg" className="sm:w-28">
            Search
          </Button>

          <Button
            type="button"
            variant="secondary"
            size="lg"
            leadingIcon={<SlidersHorizontal aria-hidden className="size-4" />}
            onClick={() => setShowFilters((open) => !open)}
            aria-expanded={showFilters}
            className="sm:w-auto"
          >
            Filters{activeFilterCount > 0 ? ` (${activeFilterCount})` : ''}
          </Button>
        </form>

        {/* -------------------------------------------------------------- */}
        {/* Filters                                                        */}
        {/* -------------------------------------------------------------- */}
        {showFilters && (
          <Card className="mt-3 p-5">
            <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
              <Field label="Extension" htmlFor="filter-tld">
                <Select
                  id="filter-tld"
                  value={params.get('tld') ?? ''}
                  onChange={(value) => setFilter('tld', value)}
                >
                  <option value="">Any extension</option>
                  {tlds?.map((tld) => (
                    <option key={tld.tld} value={tld.tld}>
                      {tld.display}
                      {tld.listings_count > 0 ? ` (${tld.listings_count})` : ''}
                    </option>
                  ))}
                </Select>
              </Field>

              <Field label="Category" htmlFor="filter-category">
                <Select
                  id="filter-category"
                  value={params.get('category') ?? ''}
                  onChange={(value) => setFilter('category', value)}
                >
                  <option value="">Any category</option>
                  {categories?.map((category) => (
                    <option key={category.slug} value={category.slug}>
                      {category.name}
                      {category.listings_count > 0 ? ` (${category.listings_count})` : ''}
                    </option>
                  ))}
                </Select>
              </Field>

              <Field label="Maximum price" htmlFor="filter-price">
                <Select
                  id="filter-price"
                  value={params.get('max_price') ?? ''}
                  onChange={(value) => setFilter('max_price', value)}
                >
                  <option value="">Any price</option>
                  <option value="1000">Up to $1,000</option>
                  <option value="5000">Up to $5,000</option>
                  <option value="10000">Up to $10,000</option>
                  <option value="50000">Up to $50,000</option>
                </Select>
              </Field>

              <Field label="Maximum length" htmlFor="filter-length">
                <Select
                  id="filter-length"
                  value={params.get('max_length') ?? ''}
                  onChange={(value) => setFilter('max_length', value)}
                >
                  <option value="">Any length</option>
                  <option value="4">4 characters or fewer</option>
                  <option value="6">6 characters or fewer</option>
                  <option value="8">8 characters or fewer</option>
                  <option value="12">12 characters or fewer</option>
                </Select>
              </Field>
            </div>

            <div className="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2.5 border-t border-border-subtle pt-4">
              <Toggle
                id="filter-no-hyphens"
                label="No hyphens"
                checked={params.get('exclude_hyphens') === '1'}
                onChange={(on) => setFilter('exclude_hyphens', on ? '1' : null)}
              />
              <Toggle
                id="filter-no-digits"
                label="No numbers"
                checked={params.get('exclude_digits') === '1'}
                onChange={(on) => setFilter('exclude_digits', on ? '1' : null)}
              />
              <Toggle
                id="filter-premium"
                label="Premium extensions only"
                checked={params.get('premium') === '1'}
                onChange={(on) => setFilter('premium', on ? '1' : null)}
              />

              {(activeFilterCount > 0 || params.get('q') !== null) && (
                <Button variant="link" size="sm" onClick={clearAll} className="ml-auto">
                  Clear all
                </Button>
              )}
            </div>
          </Card>
        )}

        {/* -------------------------------------------------------------- */}
        {/* Results                                                        */}
        {/* -------------------------------------------------------------- */}
        {isLoading ? (
          <PageSpinner label="Searching domains" />
        ) : isError ? (
          <Alert tone="error" title="Could not load domains" className="mt-8">
            {error instanceof Error && error.message !== ''
              ? error.message
              : 'Refresh the page to try again.'}
          </Alert>
        ) : listings.length === 0 ? (
          <EmptyState
            className="mt-8"
            icon={<SearchX className="size-5" />}
            title="No domains match those filters"
            description={
              activeFilterCount > 0 || params.get('q') !== null
                ? 'Try a broader search, or clear the filters to see everything listed.'
                : 'Nothing is listed for sale yet. Verified names appear here as sellers publish them.'
            }
            action={
              activeFilterCount > 0 || params.get('q') !== null ? (
                <Button variant="secondary" onClick={clearAll}>
                  Clear filters
                </Button>
              ) : undefined
            }
          />
        ) : (
          <>
            <div className="mt-8 flex flex-wrap items-center justify-between gap-3">
              <p className="text-sm text-text-secondary">
                <span data-numeric className="font-medium text-text-primary">
                  {meta?.total ?? listings.length}
                </span>{' '}
                {(meta?.total ?? 0) === 1 ? 'domain' : 'domains'}
                {params.get('q') !== null && ` matching “${params.get('q')}”`}
              </p>

              <div className="flex items-center gap-2">
                <label htmlFor="domain-sort" className="text-sm text-text-secondary">
                  Sort
                </label>
                {/* No blank option: the API's default is a real sort order, so
                    showing it as the selected one is honest and avoids listing
                    "Recently listed" twice. */}
                <Select
                  id="domain-sort"
                  value={params.get('sort') ?? (params.get('q') !== null ? 'relevance' : 'recent')}
                  onChange={(value) => setFilter('sort', value)}
                  className="w-48"
                >
                  {SORT_OPTIONS.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </Select>
              </div>
            </div>

            {/* Dimmed while the next page loads. The previous results stay on
                screen rather than the grid blanking, which is what
                placeholderData is for. */}
            <div
              className={
                isPlaceholderData
                  ? 'mt-5 grid gap-4 opacity-60 transition-opacity sm:grid-cols-2 lg:grid-cols-3'
                  : 'mt-5 grid gap-4 transition-opacity sm:grid-cols-2 lg:grid-cols-3'
              }
            >
              {listings.map((listing) => (
                <DomainCard key={listing.id} listing={listing} />
              ))}
            </div>

            {meta !== undefined && meta.last_page > 1 && (
              <nav
                className="mt-10 flex items-center justify-center gap-3"
                aria-label="Search results pages"
              >
                <Button
                  variant="secondary"
                  disabled={meta.current_page <= 1}
                  onClick={() => goToPage(meta.current_page - 1)}
                >
                  Previous
                </Button>

                <span data-numeric className="text-sm text-text-secondary">
                  Page {meta.current_page} of {meta.last_page}
                </span>

                <Button
                  variant="secondary"
                  disabled={meta.current_page >= meta.last_page}
                  onClick={() => goToPage(meta.current_page + 1)}
                >
                  Next
                </Button>
              </nav>
            )}
          </>
        )}
      </div>
    </AppShell>
  )
}

function Field({
  label,
  htmlFor,
  children,
}: {
  label: string
  htmlFor: string
  children: React.ReactNode
}) {
  return (
    <div>
      <label htmlFor={htmlFor} className="mb-1.5 block text-sm font-medium text-text-secondary">
        {label}
      </label>
      {children}
    </div>
  )
}

/**
 * A native select, deliberately.
 *
 * A custom dropdown would have to reimplement keyboard handling, mobile
 * behaviour and screen-reader semantics that the platform already gets right,
 * and none of these filters need anything the native control cannot do.
 */
function Select({
  id,
  value,
  onChange,
  className,
  children,
}: {
  id: string
  value: string
  onChange: (value: string) => void
  className?: string
  children: React.ReactNode
}) {
  return (
    <select
      id={id}
      value={value}
      onChange={(event) => onChange(event.target.value)}
      className={`h-10 w-full rounded-md border border-border-control bg-surface px-3 text-sm text-text-primary transition-colors duration-150 hover:border-border-strong focus:border-border-accent focus:outline-none ${className ?? ''}`}
    >
      {children}
    </select>
  )
}

function Toggle({
  id,
  label,
  checked,
  onChange,
}: {
  id: string
  label: string
  checked: boolean
  onChange: (checked: boolean) => void
}) {
  return (
    <label htmlFor={id} className="inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary">
      <input
        id={id}
        type="checkbox"
        checked={checked}
        onChange={(event) => onChange(event.target.checked)}
        className="size-4 rounded border-border-control text-accent focus:ring-0 focus:ring-offset-0"
      />
      {label}
    </label>
  )
}
