'use client';

/**
 * Search - React island for the search page (FDS §4.7).
 *
 * Features:
 *  - Text input debounced 300ms
 *  - FilterShell: responsive filter chrome (desktop aside + mobile drawer)
 *  - Results as DealCard grid
 *  - Pagination: 12 per page, Prev/Next + numbered pages
 */

import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { announce } from '@/lib/a11y/announce';
import { type SearchSort } from '@/lib/enums/search-sort';
import type { DehydratedState } from '@tanstack/react-query';
import { keepPreviousData, useQuery } from '@tanstack/react-query';
import { buildFacetKey } from '@/lib/deals/facet-key';
import type { DealsFacetCounts } from '@/server/db/queries/deals-facets';
import { HISTO_BUCKETS } from '@/lib/deals/histo-buckets';
import { AppShell } from '@/components/ui/layout/AppShell';
import { Container } from '@/components/ui/layout/Container';
import { HydratedIsland } from '@/components/HydratedIsland';
import { WishlistProvider } from '@/features/wishlist/WishlistContext';
import { BottomNav, useCustomerNavItems } from '@/components/ui/layout/BottomNav';
import { Grid } from '@/components/ui/layout/Grid';
import { Breadcrumb } from '@/components/ui/layout/Breadcrumb';
import { DealCard } from '@/components/ui/domain/DealCard';
import type { DealCardDeal } from '@/components/ui/domain/DealCard';
import { AmbientDealCard } from '@/features/deals/AmbientDealCard';
import { VendorResultCard } from '@/components/ui/domain/VendorResultCard';
import type { VendorSearchResult } from '@/server/catalog/public/search';
import { EmptyState } from '@/components/ui/feedback/EmptyState';
import { ErrorState } from '@/components/ui/feedback/ErrorState';
import { SearchResultsSkeleton, SkeletonGuard } from '@/components/ui/feedback/Skeleton';
import { Spinner } from '@/components/ui/feedback/Spinner';
import { Button } from '@/components/ui/primitives/Button';
import { Icon } from '@/components/ui/icons/Icon';
import { SearchAutocomplete } from '@/components/ui/overlays/SearchAutocomplete';
import type { SuggestionItem } from '@/components/ui/overlays/SearchAutocomplete';
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from '@/components/ui/primitives/Select';
import { Pagination } from '@/components/ui/primitives/Pagination';
import { SiteNav } from '@/components/ui/layout/SiteNav';
import { GlobalCartDrawer } from '@/components/ui/domain/cart/GlobalCartDrawer';
import { AuthGateModal } from '@/features/auth-flow/AuthGateModal';
import { CheckoutModal } from '@/features/checkout-modal/CheckoutModal';
import { useT } from '@/lib/i18n/react';
import { FilterShell } from '@/components/ui/domain/FilterShell';
import type { ActiveChip } from '@/components/ui/domain/FilterShell';
import { CategoryFacet } from '@/components/ui/domain/facets/CategoryFacet';
import type { CategoryOption } from '@/components/ui/domain/facets/CategoryFacet';
import { TagFacet } from '@/components/ui/domain/facets/TagFacet';
import type { TagOption } from '@/components/ui/domain/facets/TagFacet';
import { PriceRangeFacet } from '@/components/ui/domain/facets/PriceRangeFacet';
import { DiscountTierFacet } from '@/components/ui/domain/facets/DiscountTierFacet';
import type { DiscountTier } from '@/components/ui/domain/facets/DiscountTierFacet';
import { DealTypeFacet } from '@/components/ui/domain/facets/DealTypeFacet';
import type { DealType } from '@/lib/deal-types';
import { dealTypeMeta } from '@/lib/deal-types';
import { RecentSearches } from './RecentSearches';
import { useRecentSearches } from './useRecentSearches';
import { searchDeals } from './SearchClient';
import { useCachedFilterData } from './useCachedFilterData';
import type { FilterData } from './useCachedFilterData';
import type { Locale } from '@/lib/i18n';
import { pickLocalized } from '@/lib/i18n';
import { interpolate } from '@/lib/i18n/interpolate';
import { formatShekelFloat } from '@/lib/money.js';
import {
  useUrlFilterState,
  type UrlCodec,
  type UseUrlFilterStateReturn,
} from '@/lib/url/useUrlFilterState';
import { makeSearchCodec, type SearchUrlState } from '@/lib/url/codecs/searchCodec';
import { HISTORY } from '@/lib/url/historyPolicy';

const PAGE_SIZE = 12;

function buildFacetsUrl(params: {
  locale: Locale;
  type?: string;
  catSlug?: string;
  tagSlugs: string[];
  priceMin?: number;
  priceMax?: number;
  q?: string;
  minDiscount?: number;
  /** Slider ceiling — histogram domain must match so bars align to the track. */
  histoMin: number;
  histoMax: number;
}): string {
  const api = new URLSearchParams();
  api.set('locale', params.locale);
  if (params.type) api.set('type', params.type);
  if (params.catSlug) api.set('cat', params.catSlug);
  if (params.tagSlugs.length > 0) api.set('tagSlugs', params.tagSlugs.join(','));
  if (params.priceMin != null) api.set('priceMin', String(params.priceMin));
  if (params.priceMax != null) api.set('priceMax', String(params.priceMax));
  if (params.q?.trim()) api.set('q', params.q.trim());
  if (params.minDiscount != null) api.set('minDiscount', String(params.minDiscount));
  api.set('limit', '30');
  api.set('histoMin', String(params.histoMin));
  api.set('histoMax', String(params.histoMax));
  api.set('histoBuckets', String(HISTO_BUCKETS));
  return `/api/deals/facets?${api.toString()}`;
}

async function fetchFacets(params: {
  locale: Locale;
  type?: string;
  catSlug?: string;
  tagSlugs: string[];
  priceMin?: number;
  priceMax?: number;
  q?: string;
  minDiscount?: number;
  histoMin: number;
  histoMax: number;
}): Promise<DealsFacetCounts> {
  const res = await fetch(buildFacetsUrl(params));
  if (!res.ok) throw new Error(`facets fetch ${res.status}`);
  // respondOk flat-merges the payload: wire is { ok, ...DealsFacetCounts } — no `data` key.
  const json = (await res.json()) as { ok: boolean } & DealsFacetCounts;
  return {
    categories: json.categories,
    types: json.types,
    tags: json.tags,
    priceHistogram: json.priceHistogram,
  };
}

export interface SearchProps {
  locale: Locale;
  /** SSR-seeded URL filter state (codec.parse of canonical URL). */
  initialUrlState?: SearchUrlState;
  /** True when no session exists. */
  isGuest?: boolean;
  /** True when the current user is an admin. */
  isAdmin?: boolean;
  /** True when the current user is a vendor. */
  isVendor?: boolean;
  userName?: string;
  /** SSR-prefetched first-page results. */
  initialResults?: DealCardDeal[];
  /** SSR-prefetched vendor matches (page 1 only). */
  initialVendors?: VendorSearchResult[];
  /** Total result count for SSR-prefetched page. */
  initialTotal?: number;
  /** Total pages for SSR-prefetched results. */
  initialTotalPages?: number;
  /** SSR-prefetched filter sidebar data (categories, topTags, priceCeiling). */
  initialFilterData?: FilterData;
  /** Dehydrated react-query state from SSR (seeds HydrationBoundary). */
  dehydratedState?: DehydratedState;
}

type SortValue = SearchSort;

function SearchInner({
  locale,
  initialUrlState,
  isGuest = true,
  isAdmin = false,
  isVendor = false,
  userName,
  initialResults = [],
  initialVendors = [],
  initialTotal = 0,
  initialTotalPages = 1,
  initialFilterData,
}: SearchProps) {
  const t = useT('search');
  const tCommon = useT('common');
  const tNav = useT('nav');
  const tNearYou = useT('near_you');
  const tDealType = useT('domain_deal_type');

  // Fallback filter data for when SSR data is unavailable (e.g. local dev / db=null)
  const fallbackFilterData: FilterData = {
    categories: [],
    topTags: [],
    otherFilters: { priceFloor: 0, priceCeiling: 2000, discountRange: { min: 1, max: 100 } },
  };

  const {
    categories: allCategories,
    topTags: rawCachedTopTags,
    otherFilters,
  } = useCachedFilterData(initialFilterData ?? fallbackFilterData, locale);

  // Map TagWithCount → TagOption for TagFacet (key = slug, name = locale-resolved).
  const tagOptions: TagOption[] = rawCachedTopTags.map((tg) => ({ key: tg.slug, name: tg.name }));

  const priceCeiling = otherFilters.priceCeiling ?? 2000;
  const priceFloor = otherFilters.priceFloor ?? 0;

  const codec = useMemo(() => makeSearchCodec({ locale, priceCeiling }), [locale, priceCeiling]);

  const { state, setUrlState } = useUrlFilterState({
    initial: (initialUrlState ?? {
      q: '',
      categorySlug: undefined,
      tagSlugs: [],
      sort: 'relevance',
      page: 1,
      minPrice: 0,
      maxPrice: priceCeiling,
      types: [],
      discountTiers: [],
    }) as SearchUrlState & Record<string, unknown>,
    codec: codec as unknown as UrlCodec<SearchUrlState & Record<string, unknown>>,
  }) as UseUrlFilterStateReturn<SearchUrlState>;

  // CategoryFacet is ID-based; codec stores a single slug — resolve slug→id for the facet.
  const categoryIds = useMemo(() => {
    if (!state.categorySlug) return [];
    const match = allCategories.find((c) => c.slug === state.categorySlug);
    return match ? [match.id] : [];
  }, [allCategories, state.categorySlug]);

  const discountTiersNumeric = useMemo(
    () => state.discountTiers.map((d) => Number(d) as DiscountTier),
    [state.discountTiers],
  );

  // Data state — seeded from SSR prefetch
  const [results, setResults] = useState<DealCardDeal[]>(initialResults);
  const [vendors, setVendors] = useState<VendorSearchResult[]>(initialVendors ?? []);
  const [total, setTotal] = useState(initialTotal);
  const [totalPages, setTotalPages] = useState(initialTotalPages);
  const [loading, setLoading] = useState(false);
  const [hasSearched, setHasSearched] = useState(true);
  // Skip the first auto-search triggered by the debounce effect — SSR already provided data
  const skipFirstAutoSearchRef = useRef(true);
  const pendingAnnouncementRef = useRef(false);
  // SSR provided a settled result set on mount → count it as "fetched once" so a
  // typing-triggered refetch never swaps the grid for the full skeleton. See spec.
  const [hasFetchedOnce, setHasFetchedOnce] = useState(initialResults.length > 0);

  const {
    searches: recentSearches,
    add: addRecentSearch,
    clear: clearRecentSearches,
  } = useRecentSearches();
  const trendingSearches = useMemo(
    () =>
      [
        t('trending_search_1'),
        t('trending_search_2'),
        t('trending_search_3'),
        t('trending_search_4'),
        t('trending_search_5'),
        t('trending_search_6'),
      ].filter((value, index, all) => value.trim().length > 0 && all.indexOf(value) === index),
    [t],
  );
  const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const navItems = useCustomerNavItems('/search');

  const facetType = state.types.length === 1 ? state.types[0] : undefined;
  const facetPriceMin = state.minPrice > priceFloor ? state.minPrice : undefined;
  const facetPriceMax =
    state.maxPrice > 0 && state.maxPrice < priceCeiling ? state.maxPrice : undefined;
  const priceRange = useMemo<[number, number]>(
    () => [facetPriceMin ?? priceFloor, facetPriceMax ?? priceCeiling],
    [facetPriceMin, facetPriceMax, priceFloor, priceCeiling],
  );
  const facetMinDiscount =
    discountTiersNumeric.length > 0 ? Math.min(...discountTiersNumeric) : undefined;
  const facetParams = {
    locale,
    catSlug: state.categorySlug,
    type: facetType,
    tagSlugs: state.tagSlugs,
    priceMin: facetPriceMin,
    priceMax: facetPriceMax,
    minDiscount: facetMinDiscount,
    // Histogram domain = slider ceiling so bars align to the track. buildFacetKey ignores this.
    histoMin: priceFloor,
    histoMax: priceCeiling,
  };

  const {
    data: facets,
    isError,
    refetch,
  } = useQuery<DealsFacetCounts>({
    queryKey: buildFacetKey(facetParams),
    queryFn: () => fetchFacets(facetParams),
    staleTime: 60_000,
    placeholderData: keepPreviousData,
    refetchOnWindowFocus: false,
  });

  const facetCategoryCounts = new Map((facets?.categories ?? []).map((c) => [c.slug, c.dealCount]));
  const categoryOptions: CategoryOption[] = allCategories.map((c) => ({
    id: c.id,
    slug: c.slug,
    nameHe: c.nameHe,
    nameEn: c.nameEn,
    dealCount: facetCategoryCounts.get(c.slug),
  }));

  const facetTagCounts = new Map((facets?.tags ?? []).map((t) => [t.slug, t.usageCount]));
  const tagsWithCounts: TagOption[] = tagOptions.map((tg) => ({
    ...tg,
    count: facetTagCounts.get(tg.key),
  }));

  const doSearch = useCallback(
    async (
      q: string,
      catIds: string[],
      pr: [number, number],
      ceiling: number,
      tiers: DiscountTier[],
      types: DealType[],
      s: SortValue,
      pg: number,
      tgSlugs: string[],
    ) => {
      setLoading(true);
      setHasSearched(true);

      // Compute minDiscount: send min of selected tiers; empty = no filter
      const minDiscount = tiers.length > 0 ? Math.min(...tiers) : undefined;

      // Resolve category ID → slug for API (single-cat supported by search.ts)
      const catSlug =
        catIds.length === 1 ? allCategories.find((c) => c.id === catIds[0])?.slug : undefined;

      try {
        const resp = await searchDeals({
          q,
          locale,
          catSlug,
          tagSlugs: tgSlugs.length > 0 ? tgSlugs : undefined,
          minPrice: pr[0] > priceFloor ? pr[0] : undefined,
          maxPrice: pr[1] < ceiling ? pr[1] : undefined,
          priceCeiling: ceiling,
          minDiscount,
          type: types.length === 1 ? types[0] : undefined,
          sort: s,
          page: pg,
          pageSize: PAGE_SIZE,
        });
        const newResults = resp.ok ? resp.results : [];
        setResults(newResults);
        setVendors(resp.ok ? (resp.vendors ?? []) : []);
        if (resp.total != null) setTotal(resp.total);
        // Use server totalPages if provided, otherwise compute from total
        if (resp.totalPages != null) {
          setTotalPages(resp.totalPages);
        } else if (resp.total != null) {
          setTotalPages(Math.max(1, Math.ceil(resp.total / PAGE_SIZE)));
        } else {
          // Fallback: if we got a full page, assume there may be more
          setTotalPages(newResults.length >= PAGE_SIZE ? pg + 1 : pg);
        }
      } finally {
        setLoading(false);
        setHasFetchedOnce(true);
      }
    },
    [allCategories, locale, priceFloor],
  );

  // Single reactive refetch path — watches URL-backed state (including page).
  // Skip the very first trigger (SSR already provided results).
  useEffect(() => {
    if (skipFirstAutoSearchRef.current) {
      skipFirstAutoSearchRef.current = false;
      return;
    }
    clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      pendingAnnouncementRef.current = true;
      void doSearch(
        state.q,
        categoryIds,
        priceRange,
        priceCeiling,
        discountTiersNumeric,
        state.types,
        state.sort as SortValue,
        state.page,
        state.tagSlugs,
      );
    }, 300);
    return () => clearTimeout(debounceRef.current);
  }, [
    state.q,
    state.categorySlug,
    state.tagSlugs,
    state.sort,
    state.page,
    state.minPrice,
    state.maxPrice,
    state.types,
    state.discountTiers,
    categoryIds,
    discountTiersNumeric,
    priceRange,
    doSearch,
    priceCeiling,
  ]);

  const handlePageChange = useCallback(
    (newPage: number) => {
      setUrlState({ page: newPage }, HISTORY.tweak);
    },
    [setUrlState],
  );

  const handleQuerySubmit = useCallback(
    (q: string) => {
      const trimmed = q.trim();
      if (trimmed) {
        addRecentSearch(trimmed);
        setUrlState({ q: trimmed, page: 1 }, HISTORY.nav);
      }
    },
    [addRecentSearch, setUrlState],
  );

  const handleRecentSelect = (q: string) => {
    const trimmed = q.trim();
    setUrlState({ q: trimmed, page: 1 }, HISTORY.nav);
    if (trimmed) addRecentSearch(trimmed);
  };

  const handleSuggestionSelect = useCallback(
    (item: SuggestionItem) => {
      const label =
        item.type === 'deal'
          ? item.deal.title
          : item.type === 'vendor'
            ? item.vendor.displayName
            : item.type === 'category'
              ? item.entity.name
              : item.type === 'tag'
                ? item.entity.name
                : item.query; // 'search'
      const trimmed = label.trim();
      setUrlState({ q: trimmed, page: 1 }, HISTORY.nav);
      if (trimmed) addRecentSearch(trimmed);
    },
    [addRecentSearch, setUrlState],
  );

  function handleClearAll() {
    setUrlState(
      {
        categorySlug: undefined,
        minPrice: undefined,
        maxPrice: undefined,
        discountTiers: [],
        types: [],
        tagSlugs: [],
        page: 1,
      },
      HISTORY.nav,
    );
  }

  const isInitialLoading = loading && results.length === 0 && !hasFetchedOnce;
  const isRefetching = loading && !isInitialLoading;

  const resultCountLabel =
    isInitialLoading || !hasSearched
      ? ''
      : total === 0
        ? t('results_count_zero')
        : total === 1
          ? t('results_count_one')
          : totalPages > 1
            ? t('results_count_paged')
                .replace('{{shown}}', String(results.length))
                .replace('{{total}}', String(total))
            : t('results_count_other').replace('{{count}}', String(total));

  useEffect(() => {
    if (!pendingAnnouncementRef.current || loading || !resultCountLabel) return;
    announce({
      message: resultCountLabel,
      dedupeKey: [
        'search-results',
        state.q,
        state.page,
        state.categorySlug ?? '',
        state.tagSlugs.join(','),
        state.types.join(','),
        state.discountTiers.join(','),
        state.minPrice ?? '',
        state.maxPrice ?? '',
        state.sort,
      ].join(':'),
    });
    pendingAnnouncementRef.current = false;
  }, [loading, resultCountLabel, state]);

  const resultsContent = isInitialLoading ? (
    <div aria-busy="true" role="status">
      <span className="sr-only">{t('loading')}</span>
      <SkeletonGuard>
        <SearchResultsSkeleton count={8} />
      </SkeletonGuard>
    </div>
  ) : !loading && hasSearched && results.length === 0 && vendors.length === 0 ? (
    <EmptyState
      icon={<Icon name="Search" size="lg" />}
      title={t('no_results_title')}
      description={t('no_results_desc')}
      action={
        <Button
          variant="primary"
          size="md"
          onClick={() => {
            window.open('https://vendors.multideal.co.il', '_blank', 'noopener,noreferrer');
          }}
        >
          {t('no_results_cta')}
        </Button>
      }
    />
  ) : null;

  const activeChips: ActiveChip[] = [
    state.types.length > 0 && state.types.length < 2
      ? {
          label: state.types.map((dt) => tDealType(dealTypeMeta(dt).labelKey)).join(', '),
          onRemove: () => setUrlState({ types: [], page: 1 }, HISTORY.tweak),
        }
      : null,
    state.categorySlug
      ? (() => {
          const cat = allCategories.find((c) => c.slug === state.categorySlug);
          return {
            label: cat ? pickLocalized(cat, locale) : state.categorySlug,
            onRemove: () => setUrlState({ categorySlug: undefined, page: 1 }, HISTORY.tweak),
          };
        })()
      : null,
    facetPriceMin != null || facetPriceMax != null
      ? {
          label: `${formatShekelFloat(priceRange[0])}–${formatShekelFloat(priceRange[1])}`,
          onRemove: () =>
            setUrlState({ minPrice: 0, maxPrice: priceCeiling, page: 1 }, HISTORY.tweak),
        }
      : null,
    discountTiersNumeric.length > 0
      ? {
          label: discountTiersNumeric.map((d) => `${d}%+`).join(', '),
          onRemove: () => setUrlState({ discountTiers: [], page: 1 }, HISTORY.tweak),
        }
      : null,
    ...state.tagSlugs.map((slug) => {
      const tag = tagOptions.find((tg) => tg.key === slug);
      return {
        label: tag?.name ?? slug,
        onRemove: () =>
          setUrlState(
            { tagSlugs: state.tagSlugs.filter((s) => s !== slug), page: 1 },
            HISTORY.tweak,
          ),
      };
    }),
  ].filter(Boolean) as ActiveChip[];

  if (isError) {
    return (
      <ErrorState
        title={tCommon('error_loading')}
        action={
          <Button variant="secondary" size="sm" onClick={() => refetch()}>
            {tCommon('retry')}
          </Button>
        }
      />
    );
  }
  return (
    <AppShell
      mode="customer"
      desktopTopBar={
        <SiteNav
          variant="desktop"
          currentPath="/search"
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
          userName={userName}
        />
      }
      topBar={
        <SiteNav
          variant="mobile"
          title={tNav('search')}
          currentPath="/search"
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
        />
      }
      bottomNav={<BottomNav mode="customer" items={navItems} />}
      pageOverlays={
        <>
          <GlobalCartDrawer />
          <AuthGateModal />
        </>
      }
    >
      <Container maxWidth="4xl" px="6">
        <Breadcrumb items={[{ label: tNav('home'), href: '/' }, { label: tNav('search') }]} />

        <div className="py-4 lg:py-6">
          {/* Search bar + sort + count (all breakpoints) */}
          <div className="mb-4 flex flex-wrap items-center gap-3">
            <SearchAutocomplete
              query={state.q}
              onQueryChange={(q) => setUrlState({ q, page: 1 }, HISTORY.tweak)}
              onSelect={handleSuggestionSelect}
              onSubmit={(q) => void handleQuerySubmit(q)}
              locale={locale}
              placeholder={t('placeholder')}
              className="flex-1"
            />
            {resultCountLabel && (
              <span className="text-text-secondary shrink-0 text-sm tabular-nums">
                {resultCountLabel}
              </span>
            )}
            <Select
              value={state.sort}
              onValueChange={(v) => setUrlState({ sort: v, page: 1 }, HISTORY.tweak)}
            >
              <SelectTrigger aria-label={t('sort_by')} className="w-40">
                <SelectValue placeholder={t('sort_by')} />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="relevance">{t('sort_relevance')}</SelectItem>
                <SelectItem value="price_asc">{t('sort_price_asc')}</SelectItem>
                <SelectItem value="price_desc">{t('sort_price_desc')}</SelectItem>
              </SelectContent>
            </Select>
          </div>

          {/* FilterShell + results */}
          <div className="flex items-start gap-6">
            <FilterShell
              activeChips={activeChips}
              onClearAll={handleClearAll}
              filterLabel={tNearYou('filter')}
              clearAllLabel={tNearYou('clearAll')}
              applyLabel={tNearYou('applyFilters')}
            >
              <CategoryFacet
                label={t('category_label')}
                categories={categoryOptions}
                selectedIds={categoryIds}
                onToggle={(id) => {
                  const cat = allCategories.find((c) => c.id === id);
                  const slug = cat?.slug;
                  if (!slug) return;
                  const nextSlug = state.categorySlug === slug ? undefined : slug;
                  setUrlState({ categorySlug: nextSlug, page: 1 }, HISTORY.nav);
                }}
                locale={locale}
                showCounts={true}
                countPlacement="inline"
              />
              <TagFacet
                label={t('tag_filter_label')}
                tags={tagsWithCounts}
                selected={state.tagSlugs}
                onToggle={(key) => {
                  const next = state.tagSlugs.includes(key)
                    ? state.tagSlugs.filter((s) => s !== key)
                    : [...state.tagSlugs, key];
                  setUrlState({ tagSlugs: next, page: 1 }, HISTORY.tweak);
                }}
              />
              <PriceRangeFacet
                label={t('price_filter')}
                floor={priceFloor}
                ceiling={priceCeiling}
                value={priceRange}
                step={10}
                debounceMs={0}
                onChange={([lo, hi]) =>
                  setUrlState(
                    {
                      minPrice: lo > priceFloor ? lo : 0,
                      maxPrice: hi < priceCeiling ? hi : priceCeiling,
                      page: 1,
                    },
                    HISTORY.tweak,
                  )
                }
                histogram={facets?.priceHistogram}
                formatBarTooltip={(count, lo, hi) =>
                  interpolate(t('histogram_bar_tooltip'), {
                    count,
                    range: `${formatShekelFloat(Math.round(lo))}–${formatShekelFloat(Math.round(hi))}`,
                  })
                }
              />
              <DiscountTierFacet
                label={t('discount_filter')}
                tierLabels={{
                  50: t('min_discount_50'),
                  60: t('min_discount_60'),
                  70: t('min_discount_70'),
                }}
                selected={discountTiersNumeric}
                onChange={(tiers) =>
                  setUrlState({ discountTiers: tiers.map(String), page: 1 }, HISTORY.tweak)
                }
              />
              <DealTypeFacet
                label={t('deal_type_label')}
                selected={state.types}
                onChange={(types) => setUrlState({ types, page: 1 }, HISTORY.tweak)}
                showCounts
                counts={facets?.types}
                countPlacement="inline"
              />
            </FilterShell>

            {/* Results */}
            <div className="min-w-0 flex-1">
              {/* Recent searches — mobile only, shown when no active query */}
              {!state.q.trim() ? (
                <div className="mb-4 flex flex-col gap-4">
                  <RecentSearches
                    title={t('recent_searches')}
                    searches={recentSearches}
                    onSelect={handleRecentSelect}
                    onClear={clearRecentSearches}
                  />
                  <RecentSearches
                    title={t('trending_searches')}
                    searches={trendingSearches}
                    onSelect={handleRecentSelect}
                  />
                </div>
              ) : null}

              {}
              {hasSearched && !isInitialLoading && (results.length > 0 || vendors.length > 0) && (
                <h2 className="text-text-primary mb-3 font-bold lg:hidden">
                  {t('results_heading')}
                </h2>
              )}

              {resultsContent ?? (
                <div className="relative" aria-busy={isRefetching || undefined}>
                  {isRefetching && (
                    <div
                      className="bg-surface-base/60 absolute inset-0 z-10 flex items-start justify-center pt-12"
                      role="status"
                    >
                      <span className="sr-only">{t('loading')}</span>
                      <Spinner />
                    </div>
                  )}
                  <div className={isRefetching ? 'pointer-events-none opacity-60' : undefined}>
                    <Grid cols={1} lgCols={3} gap="4">
                      {vendors.flatMap((v) => [
                        <VendorResultCard key={`v-${v.id}`} vendor={v} />,
                        ...v.deals.map((d) => (
                          <AmbientDealCard key={`vd-${d.id}`} imageSrc={d.imageSrc}>
                            <DealCard deal={d} variant="grid" />
                          </AmbientDealCard>
                        )),
                      ])}
                      {results.map((deal) => (
                        <AmbientDealCard key={deal.id} imageSrc={deal.imageSrc}>
                          <DealCard deal={deal} variant="grid" />
                        </AmbientDealCard>
                      ))}
                    </Grid>
                    <div className="mt-6">
                      <Pagination
                        page={state.page}
                        totalPages={totalPages}
                        onPageChange={handlePageChange}
                        isLoading={loading}
                      />
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </Container>
    </AppShell>
  );
}

export function Search({
  locale,
  initialUrlState,
  isGuest,
  isAdmin,
  isVendor,
  userName,
  initialResults,
  initialVendors,
  initialTotal,
  initialTotalPages,
  initialFilterData,
  dehydratedState,
}: SearchProps) {
  return (
    <HydratedIsland dehydratedState={dehydratedState} locale={locale}>
      <WishlistProvider>
        <SearchInner
          locale={locale}
          initialUrlState={initialUrlState}
          isGuest={isGuest}
          isAdmin={isAdmin}
          isVendor={isVendor}
          userName={userName}
          initialResults={initialResults}
          initialVendors={initialVendors}
          initialTotal={initialTotal}
          initialTotalPages={initialTotalPages}
          initialFilterData={initialFilterData}
          dehydratedState={dehydratedState}
        />
      </WishlistProvider>
      <CheckoutModal />
    </HydratedIsland>
  );
}
