// @design-system: features/near-you/NearYouIsland
// /near-you page island — URL-synced filters, gallery view, composable facets.

'use client';

import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
import { keepPreviousData, useQuery, type DehydratedState } from '@tanstack/react-query';
import { HydratedIsland } from '@/components/HydratedIsland';
import { WishlistProvider } from '@/features/wishlist/WishlistContext';
import type { ActiveChip } from '@/components/ui/domain/FilterShell';
import { BrowseFilterSidebar } from '@/components/ui/domain/BrowseFilterSidebar';
import { CitySelector } from '@/features/near-you/CitySelector';
import type { CityOption } from '@/components/ui/domain/CitySelector';
import { RadiusSlider } from '@/components/ui/domain/RadiusSlider';
import { OpeningHoursFilter } from '@/components/ui/domain/OpeningHoursFilter';
import type { OpeningHoursValue } from '@/components/ui/domain/OpeningHoursFilter';
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 { DealTypeFacet } from '@/components/ui/domain/facets/DealTypeFacet';
import { PriceRangeFacet } from '@/components/ui/domain/facets/PriceRangeFacet';
import { dealTypeMeta, type DealType } from '@/lib/deal-types';
import { ViewToggle } from '@/components/ui/domain/ViewToggle';
import type { ViewMode } from '@/components/ui/domain/ViewToggle';
import { GalleryView } from '@/components/ui/domain/DealsBrowser/GalleryView';
import { MapView } from '@/components/ui/domain/MapView';
import { Button } from '@/components/ui/primitives/Button';
import { Pagination } from '@/components/ui/primitives/Pagination';
import { useFeedQuery } from '@/lib/hooks/useFeedQuery';
import { useMarkersQuery } from '@/lib/hooks/useMarkersQuery';
import { useCityPreference } from '@/lib/hooks/useCityPreference';
import { Container } from '@/components/ui/layout/Container';
import { useCityStore } from '@/lib/state/cityStore';
import { useT } from '@/lib/i18n/react';
import { interpolate } from '@/lib/i18n/interpolate';
import { formatAgorotWhole } from '@/lib/money';
import { useCachedFilterData } from '@/features/search/useCachedFilterData';
import type { FilterData } from '@/features/search/useCachedFilterData';
import { captureCaught } from '@/lib/observability';
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 type { FeedFilter } from '@/server/schemas/feed';
import {
  useUrlFilterState,
  type UrlCodec,
  type UseUrlFilterStateReturn,
} from '@/lib/url/useUrlFilterState';
import { makeNearYouCodec, type NearYouUrlState } from '@/lib/url/codecs/nearYouCodec';
import { HISTORY } from '@/lib/url/historyPolicy';

// ─── Types ────────────────────────────────────────────────────────────────────

export interface FilterState {
  cityCode: string | null;
  radius: number;
  hours: OpeningHoursValue;
  dealTypes: DealType[];
  categoryId: string | null;
  tagIds: string[];
  minPrice: number;
  maxPrice: number;
}

// ─── Constants ────────────────────────────────────────────────────────────────

const DEFAULT_FILTER_STATE: FilterState = {
  cityCode: null,
  radius: 10,
  hours: { mode: 'any' },
  dealTypes: [],
  categoryId: null,
  tagIds: [],
  minPrice: 0,
  maxPrice: 2000,
};

const DEFAULT_FILTER_STATE_AS_URLSTATE: NearYouUrlState = {
  cityCode: null,
  radius: DEFAULT_FILTER_STATE.radius,
  hours: DEFAULT_FILTER_STATE.hours,
  dealTypes: [],
  tagIds: [],
  minPrice: 0,
  maxPrice: 2000,
  view: 'gallery',
  page: 1,
};

// ─── Facets fetch (scoped counts for sidebar) ─────────────────────────────────

function buildFacetsUrl(params: {
  locale: 'he' | 'en';
  type?: string;
  catSlug?: string;
  tagSlugs: string[];
  cityCode?: string;
  lat?: number;
  lng?: number;
  km?: number;
  priceMin?: number;
  priceMax?: number;
  priceFloor: number;
  priceCeiling: 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.cityCode) api.set('cityCode', params.cityCode);
  if (params.lat != null && params.lng != null && params.km != null) {
    api.set('lat', String(params.lat));
    api.set('lng', String(params.lng));
    api.set('km', String(params.km));
  }
  if (params.priceMin != null) api.set('priceMin', String(params.priceMin));
  if (params.priceMax != null) api.set('priceMax', String(params.priceMax));
  api.set('histoMin', String(params.priceFloor));
  api.set('histoMax', String(params.priceCeiling));
  api.set('histoBuckets', String(HISTO_BUCKETS));
  api.set('limit', '30');
  return `/api/deals/facets?${api.toString()}`;
}

async function fetchFacets(params: {
  locale: 'he' | 'en';
  type?: string;
  catSlug?: string;
  tagSlugs: string[];
  cityCode?: string;
  lat?: number;
  lng?: number;
  km?: number;
  priceMin?: number;
  priceMax?: number;
  priceFloor: number;
  priceCeiling: 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,
  };
}

// ─── Adapter: FilterState + store.radius → FeedFilter ─────────────────────────

function toFeedFilter(
  state: FilterState,
  storeRadius: { lat: number; lng: number; km: number } | null,
  paging: { page?: number; limit?: number },
  priceBounds: { floor: number; ceiling: number },
): FeedFilter {
  const minPrice = state.minPrice > priceBounds.floor ? state.minPrice : undefined;
  const maxPrice =
    state.maxPrice > 0 && state.maxPrice < priceBounds.ceiling ? state.maxPrice : undefined;

  return {
    cityCode: state.cityCode ?? undefined,
    radius: storeRadius
      ? { lat: storeRadius.lat, lng: storeRadius.lng, km: state.radius }
      : undefined,
    hours: state.hours,
    dealType: state.dealTypes.length === 1 ? state.dealTypes[0] : undefined,
    categoryId: state.categoryId ?? undefined,
    tagIds: state.tagIds.length > 0 ? state.tagIds : undefined,
    minPrice,
    maxPrice,
    page: paging.page,
    limit: paging.limit ?? 20,
    preset: 'near-you',
  };
}

// ─── Inner island (inside HydratedIsland / QueryClientProvider) ──────────────

interface NearYouInnerProps {
  locale?: 'he' | 'en';
  initialUrlState?: NearYouUrlState;
  initialFilterData?: FilterData;
  priceCeiling?: number;
}

function NearYouInner({
  locale = 'he',
  initialUrlState,
  initialFilterData,
  priceCeiling: priceCeilingProp = 2000,
}: NearYouInnerProps) {
  const t = useT('near_you');
  const tDealType = useT('domain_deal_type');
  const { cityCode: storedCityCode } = useCityPreference();
  const storeRadius = useCityStore((s) => s.radius);
  const locationGranted = storeRadius !== null;

  // Defer location scope until post-mount so first-render facet key stays global (SSR seed parity).
  // useSyncExternalStore returns false during SSR and true after client mount — no setState-in-effect.
  const locationScopeReady = useSyncExternalStore(
    () => () => {},
    () => true,
    () => false,
  );

  // Categories + tags via shared hook (SSR seed → LS cache → react-query revalidation)
  const fallbackFilterData: FilterData = {
    categories: [],
    topTags: [],
    otherFilters: {
      priceFloor: 0,
      priceCeiling: priceCeilingProp,
      discountRange: { min: 1, max: 100 },
    },
  };
  const { categories, topTags, otherFilters } = useCachedFilterData(
    initialFilterData ?? fallbackFilterData,
    locale,
  );
  const priceCeiling = otherFilters.priceCeiling ?? priceCeilingProp;
  const priceFloor = otherFilters.priceFloor ?? 0;

  // Cities for chip label resolution (CitySelector self-fetches; this is parallel for chip only)
  const [cities, setCities] = useState<CityOption[]>([]);
  useEffect(() => {
    fetch('/api/cities?v=2')
      .then((r) => r.json())
      .then((d) => {
        const res = d as { cities?: CityOption[] };
        if (res.cities) setCities(res.cities.filter((c) => c.cityCode && c.city));
      })
      .catch((err: unknown) => captureCaught(err, { scope: 'NearYouIsland.fetchCities' }));
  }, [storedCityCode]);

  const codec = useMemo(() => makeNearYouCodec({ locale, priceCeiling }), [locale, priceCeiling]);
  const { state, setUrlState } = useUrlFilterState({
    initial: (initialUrlState ?? {
      ...DEFAULT_FILTER_STATE_AS_URLSTATE,
      minPrice: 0,
      maxPrice: priceCeiling,
    }) as NearYouUrlState & Record<string, unknown>,
    codec: codec as unknown as UrlCodec<NearYouUrlState & Record<string, unknown>>,
  }) as UseUrlFilterStateReturn<NearYouUrlState>;

  const [mapViewport, setMapViewport] = useState<{ lat: number; lng: number; km: number } | null>(
    null,
  );

  // Restore last-used city when URL carries none (reads location directly for effect-order robustness).
  useEffect(() => {
    const parsed = codec.parse({
      pathname: window.location.pathname,
      search: window.location.search,
    });
    if (!parsed.cityCode && storedCityCode) {
      setUrlState({ ...parsed, cityCode: storedCityCode }, HISTORY.tweak);
    }
  }, [codec, setUrlState, storedCityCode]);

  const skipFirstStoreRadiusRef = useRef(true);
  useEffect(() => {
    if (skipFirstStoreRadiusRef.current) {
      skipFirstStoreRadiusRef.current = false;
      return;
    }
    setUrlState({ page: 1 }, HISTORY.tweak);
    if (state.view !== 'map') {
      setTimeout(() => setMapViewport(null), 0);
    }
  }, [setUrlState, state.view, storeRadius]);

  const filterDims: FilterState = {
    cityCode: state.cityCode,
    radius: state.radius,
    hours: state.hours ?? DEFAULT_FILTER_STATE.hours,
    dealTypes: state.dealTypes,
    categoryId: state.categoryId ?? null,
    tagIds: state.tagIds,
    minPrice: state.minPrice,
    maxPrice: state.maxPrice,
  };

  const handleMapViewportChange = (vp: { lat: number; lng: number; km: number } | null) => {
    setMapViewport(vp);
    setUrlState({ page: 1 }, HISTORY.tweak);
  };

  const PAGE_SIZE = 12;
  const priceBounds = { floor: priceFloor, ceiling: priceCeiling };
  const galleryFilter = toFeedFilter(
    filterDims,
    storeRadius,
    {
      page: state.page,
      limit: PAGE_SIZE,
    },
    priceBounds,
  );
  const baseMarkersFilter = toFeedFilter(filterDims, storeRadius, {}, priceBounds);
  // When no location and no city, fall back to Tel Aviv so map always shows pins.
  const markersFilter: FeedFilter = mapViewport
    ? { ...baseMarkersFilter, radius: mapViewport, cityCode: undefined }
    : !locationGranted && !baseMarkersFilter.cityCode
      ? { ...baseMarkersFilter, cityCode: 'tel-aviv' }
      : baseMarkersFilter;

  const {
    data: galleryData,
    isFetching: galleryFetching,
    isError,
  } = useFeedQuery(galleryFilter, { enabled: state.view === 'gallery' });
  const { data: markersData } = useMarkersQuery(markersFilter, { enabled: state.view === 'map' });

  const deals = galleryData?.deals ?? [];
  const totalPages = galleryData?.totalPages ?? 1;
  const total = galleryData?.total ?? 0;
  const mapDeals = markersData?.markers ?? [];

  // Map-mode feed: same filter as markers (incl. Tel Aviv fallback) + pagination
  const mapCardsFeedFilter = { ...markersFilter, page: state.page, limit: PAGE_SIZE };
  const { data: mapCardsData, isFetching: mapCardsFetching } = useFeedQuery(mapCardsFeedFilter, {
    enabled: state.view === 'map',
  });

  const mapCards = mapCardsData?.deals ?? [];
  const mapTotalPages = mapCardsData?.totalPages ?? 1;
  const mapTotal = mapCardsData?.total ?? 0;

  // ─── Faceted counts (URL city immediate; device geo deferred until mount) ─

  const catSlug = filterDims.categoryId
    ? categories.find((c) => c.id === filterDims.categoryId)?.slug
    : undefined;
  const facetType = filterDims.dealTypes.length === 1 ? filterDims.dealTypes[0] : undefined;
  const facetPriceMin = state.minPrice > priceFloor ? state.minPrice : undefined;
  const facetPriceMax =
    state.maxPrice > 0 && state.maxPrice < priceCeiling ? state.maxPrice : undefined;
  const tagSlugs = filterDims.tagIds
    .map((id) => topTags.find((t) => t.id === id)?.slug)
    .filter((s): s is string => !!s);

  const locationScope =
    locationScopeReady && locationGranted && storeRadius
      ? { lat: storeRadius.lat, lng: storeRadius.lng, km: filterDims.radius }
      : filterDims.cityCode
        ? { cityCode: filterDims.cityCode }
        : {};

  const facetParams = {
    locale,
    catSlug,
    type: facetType,
    tagSlugs,
    priceMin: facetPriceMin,
    priceMax: facetPriceMax,
    priceFloor,
    priceCeiling,
    ...locationScope,
  };

  const { data: facets } = 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[] = categories.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 tagOptions: TagOption[] = topTags.map((tg) => ({
    key: tg.id,
    name: tg.name,
    count: facetTagCounts.get(tg.slug),
  }));

  // ─── Active chips ─────────────────────────────────────────────────────────

  const clearAllFilters = () =>
    setUrlState(
      {
        cityCode: null,
        radius: DEFAULT_FILTER_STATE.radius,
        hours: DEFAULT_FILTER_STATE.hours,
        dealTypes: [],
        categoryId: undefined,
        tagIds: [],
        minPrice: 0,
        maxPrice: priceCeiling,
        page: 1,
      },
      HISTORY.nav,
    );

  const activeChips: ActiveChip[] = [
    filterDims.cityCode
      ? {
          label:
            cities.find((c) => c.cityCode === filterDims.cityCode)?.city ?? filterDims.cityCode,
          onRemove: () => setUrlState({ cityCode: null, page: 1 }, HISTORY.tweak),
        }
      : null,
    filterDims.radius !== DEFAULT_FILTER_STATE.radius && locationGranted
      ? {
          label: `${filterDims.radius} ${t('km_unit')}`,
          onRemove: () =>
            setUrlState({ radius: DEFAULT_FILTER_STATE.radius, page: 1 }, HISTORY.tweak),
        }
      : null,
    filterDims.hours.mode !== 'any'
      ? {
          label: filterDims.hours.mode === 'openNow' ? t('openNow') : t('customWindow'),
          onRemove: () => setUrlState({ hours: { mode: 'any' }, page: 1 }, HISTORY.tweak),
        }
      : null,
    filterDims.dealTypes.length > 0 && filterDims.dealTypes.length < 2
      ? {
          label: tDealType(dealTypeMeta(filterDims.dealTypes[0]!).labelKey),
          onRemove: () => setUrlState({ dealTypes: [], page: 1 }, HISTORY.tweak),
        }
      : null,
    filterDims.categoryId
      ? {
          label:
            locale === 'he'
              ? (categories.find((c) => c.id === filterDims.categoryId)?.nameHe ??
                filterDims.categoryId)
              : (categories.find((c) => c.id === filterDims.categoryId)?.nameEn ??
                filterDims.categoryId),
          onRemove: () => setUrlState({ categoryId: undefined, page: 1 }, HISTORY.tweak),
        }
      : null,
    filterDims.tagIds.length > 0
      ? {
          label: `${t('tag_label')} ×${filterDims.tagIds.length}`,
          onRemove: () => setUrlState({ tagIds: [], page: 1 }, HISTORY.tweak),
        }
      : null,
    facetPriceMin != null || facetPriceMax != null
      ? {
          label: `${formatAgorotWhole((facetPriceMin ?? priceFloor) * 100)}–${formatAgorotWhole((facetPriceMax ?? priceCeiling) * 100)}`,
          onRemove: () =>
            setUrlState({ minPrice: 0, maxPrice: priceCeiling, page: 1 }, HISTORY.tweak),
        }
      : null,
  ].filter(Boolean) as ActiveChip[];

  const handlePageChange = (n: number) => {
    setUrlState({ page: n }, HISTORY.tweak);
    window.scrollTo({ top: 0 });
  };

  const TEL_AVIV = { lat: 32.0853, lng: 34.7818 };
  const mapCenter = storeRadius ? { lat: storeRadius.lat, lng: storeRadius.lng } : TEL_AVIV;
  const mapRadius = storeRadius ? filterDims.radius : undefined;

  return (
    <Container maxWidth="4xl" px="6" className="py-6">
      <div className="mb-4 flex items-center justify-end">
        <ViewToggle
          value={state.view as ViewMode}
          onChange={(view) => {
            setUrlState({ view, page: 1 }, HISTORY.tweak);
            if (view !== 'map') setMapViewport(null);
          }}
        />
      </div>

      <div className="flex gap-6">
        <BrowseFilterSidebar
          activeChips={activeChips}
          onClearAll={clearAllFilters}
          className="shrink-0"
        >
          <CitySelector
            value={filterDims.cityCode}
            onChange={(code) => setUrlState({ cityCode: code, page: 1 }, HISTORY.nav)}
          />
          <RadiusSlider
            value={filterDims.radius}
            onChange={(r) => setUrlState({ radius: r, page: 1 }, HISTORY.tweak)}
            disabled={!locationGranted}
          />
          <OpeningHoursFilter
            value={filterDims.hours}
            onChange={(h) => setUrlState({ hours: h, page: 1 }, HISTORY.tweak)}
          />
          <DealTypeFacet
            label={t('dealType_label')}
            selected={filterDims.dealTypes}
            onChange={(types) => setUrlState({ dealTypes: types, page: 1 }, HISTORY.tweak)}
            counts={facets?.types}
            showCounts
          />
          <CategoryFacet
            label={t('category_label')}
            categories={categoryOptions}
            selectedIds={filterDims.categoryId ? [filterDims.categoryId] : []}
            onToggle={(id) =>
              setUrlState(
                { categoryId: filterDims.categoryId === id ? undefined : id, page: 1 },
                HISTORY.nav,
              )
            }
            allLabel={t('category_all')}
            onSelectAll={() => setUrlState({ categoryId: undefined, page: 1 }, HISTORY.nav)}
            locale={locale}
            showCounts
          />
          <TagFacet
            label={t('tag_label')}
            tags={tagOptions}
            selected={filterDims.tagIds}
            onToggle={(key) => {
              const next = filterDims.tagIds.includes(key)
                ? filterDims.tagIds.filter((id) => id !== key)
                : [...filterDims.tagIds, key];
              setUrlState({ tagIds: next, page: 1 }, HISTORY.tweak);
            }}
          />
          <PriceRangeFacet
            label={t('filter_price_label')}
            floor={priceFloor}
            ceiling={priceCeiling}
            step={10}
            value={[facetPriceMin ?? priceFloor, facetPriceMax ?? priceCeiling]}
            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: `${formatAgorotWhole(Math.round(lo) * 100)}–${formatAgorotWhole(Math.round(hi) * 100)}`,
              })
            }
          />
        </BrowseFilterSidebar>

        <section className="min-w-0 flex-1" aria-live="polite">
          {isError ? (
            <div
              role="alert"
              className="flex flex-col items-center justify-center py-16 text-center"
            >
              <p className="text-text-muted text-lg">{t('emptyState_title')}</p>
              <Button variant="ghost" size="sm" onClick={clearAllFilters}>
                {t('emptyState_cta')}
              </Button>
            </div>
          ) : state.view === 'map' ? (
            <div className="flex flex-col gap-4">
              <div className="h-96">
                <MapView
                  deals={mapDeals}
                  center={mapCenter}
                  radius={mapRadius}
                  onViewportChange={handleMapViewportChange}
                />
              </div>
              {mapTotal > 0 && (
                <p className="text-text-secondary mb-3 text-sm">
                  {t('showing')
                    .replace('{shown}', String(mapCards.length))
                    .replace('{total}', String(mapTotal))}
                </p>
              )}
              <GalleryView
                deals={mapCards}
                loading={mapCardsFetching}
                gridLabel={t('title')}
                emptyLabel={t('emptyState_title')}
              />
              <Pagination
                page={state.page}
                totalPages={mapTotalPages}
                onPageChange={handlePageChange}
                isLoading={mapCardsFetching}
              />
            </div>
          ) : (
            <>
              {total > 0 && (
                <p className="text-text-secondary mb-3 text-sm">
                  {t('showing')
                    .replace('{shown}', String(deals.length))
                    .replace('{total}', String(total))}
                </p>
              )}
              <GalleryView
                deals={deals}
                loading={galleryFetching}
                gridLabel={t('title')}
                emptyLabel={t('emptyState_title')}
              />
              <Pagination
                page={state.page}
                totalPages={totalPages}
                onPageChange={handlePageChange}
                isLoading={galleryFetching}
              />
            </>
          )}
        </section>
      </div>
    </Container>
  );
}

// ─── Public export ────────────────────────────────────────────────────────────

/**
 * NearYouIsland — /near-you page island.
 *
 * Composes BrowseFilterSidebar + composable facet primitives + ViewToggle + GalleryView.
 * URL-backed filter state via useUrlFilterState (single reactive query refetch path).
 * Map mode fetches all deals in radius via /api/feed/markers (no cap, location-gated).
 *
 * Wraps with HydratedIsland for QueryClient + LocaleProvider.
 */
export interface NearYouIslandProps {
  locale?: 'he' | 'en';
  initialUrlState?: NearYouUrlState;
  initialFilterData?: FilterData;
  priceCeiling?: number;
  dehydratedState?: DehydratedState;
}

export function NearYouIsland({
  locale,
  initialUrlState,
  initialFilterData,
  priceCeiling,
  dehydratedState,
}: NearYouIslandProps) {
  return (
    <HydratedIsland locale={locale} dehydratedState={dehydratedState}>
      <WishlistProvider>
        <NearYouInner
          locale={locale}
          initialUrlState={initialUrlState}
          initialFilterData={initialFilterData}
          priceCeiling={priceCeiling}
        />
      </WishlistProvider>
    </HydratedIsland>
  );
}
