// @design-system: domain/DealsBrowser/DealsBrowserIsland
// Phase 3 React island — TanStack Query + SPA filter/sort/pagination.
// Receives SSR top-6 as initialData; fetches full page on filter/sort/page change.

'use client';

import { useCallback, useMemo } from 'react';
import { keepPreviousData, useQuery } from '@/features/query/react-query';
import { HydratedIsland } from '@/components/HydratedIsland';
import type { ActiveChip } from '@/components/ui/domain/FilterShell';
import { BrowseFilterSidebar } from '@/components/ui/domain/BrowseFilterSidebar';
import { DealTypeFacet } from '@/components/ui/domain/facets/DealTypeFacet';
import { dealTypeMeta, type DealType } from '@/lib/deal-types';
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 { PriceRangeFacet } from '@/components/ui/domain/facets/PriceRangeFacet';
import { DealCard } from '@/components/ui/domain/DealCard/DealCard';
import type { DealCardDeal } from '@/components/ui/domain/DealCard/DealCard';
import { BrowseCardSkeleton } from '@/components/ui/feedback/Skeleton/BrowseCardSkeleton';
import { SortDropdown } from '@/components/ui/domain/SortDropdown/SortDropdown';
import type { BrowseSort } from '@/lib/enums/search-sort';
import {
  useUrlFilterState,
  type UrlCodec,
  type UseUrlFilterStateReturn,
} from '@/lib/url/useUrlFilterState';
import { makeDealsCodec, type DealsUrlState } from '@/lib/url/codecs/dealsCodec';
import { makeWhatsLeftCodec } from '@/lib/url/codecs/whatsLeftCodec';
import { HISTORY } from '@/lib/url/historyPolicy';
import { dirForLocale, pickLocalized } from '@/lib/i18n';
import { useT, useLocale } from '@/lib/i18n/react';
import { interpolate } from '@/lib/i18n/interpolate';
import { formatAgorotWhole } from '@/lib/money';
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 {
  DealBrowseRow,
  DealsBrowserIslandProps,
} from '@/components/ui/domain/DealsBrowser/types';
import { Pagination } from '@/components/ui/primitives/Pagination';

const CODEC_FACTORIES = {
  deals: makeDealsCodec,
  'whats-left': makeWhatsLeftCodec,
} as const;

const PAGE_SIZE = 12;

interface BrowseApiResponse {
  ok: boolean;
  results: DealBrowseRow[];
  total: number;
  page: number;
  totalPages: number;
}

function buildBrowseUrl(params: {
  type: string;
  catSlug?: string;
  tagSlugs: string[];
  sort: BrowseSort;
  page: number;
  priceMin?: number;
  priceMax?: number;
  preset?: string;
}): string {
  const api = new URLSearchParams();
  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.sort !== 'hot') api.set('sort', params.sort);
  if (params.page > 1) api.set('page', String(params.page));
  if (params.priceMin != null) api.set('priceMin', String(params.priceMin));
  if (params.priceMax != null) api.set('priceMax', String(params.priceMax));
  if (params.preset) api.set('preset', params.preset);
  return `/api/deals/browse?${api.toString()}`;
}

async function fetchBrowse(params: {
  type: string;
  catSlug?: string;
  tagSlugs: string[];
  sort: BrowseSort;
  page: number;
  priceMin?: number;
  priceMax?: number;
  preset?: string;
}): Promise<BrowseApiResponse> {
  const res = await fetch(buildBrowseUrl(params));
  if (!res.ok) throw new Error(`browse fetch ${res.status}`);
  return res.json() as Promise<BrowseApiResponse>;
}

function buildFacetsUrl(params: {
  type: string;
  catSlug?: string;
  tagSlugs: string[];
  priceMin?: number;
  priceMax?: number;
  priceFloor: number;
  priceCeiling: number;
  preset?: string;
}): string {
  const api = new URLSearchParams();
  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.preset) api.set('preset', params.preset);
  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: {
  type: string;
  catSlug?: string;
  tagSlugs: string[];
  priceMin?: number;
  priceMax?: number;
  priceFloor: number;
  priceCeiling: number;
  preset?: string;
}): Promise<DealsFacetCounts> {
  const res = await fetch(buildFacetsUrl(params));
  if (!res.ok) throw new Error(`facets fetch ${res.status}`);
  const json = (await res.json()) as { ok: boolean } & DealsFacetCounts;
  return {
    categories: json.categories,
    types: json.types,
    tags: json.tags,
    priceHistogram: json.priceHistogram,
  };
}

function browseRowToDealCardDeal(row: DealBrowseRow): DealCardDeal {
  return {
    id: row.id,
    title: row.title,
    vendorName: row.vendorName,
    vendorAvatarUrl: row.vendorAvatarUrl ?? undefined,
    city: row.city,
    originalPrice: Number(row.originalPrice),
    discountedPrice: Number(row.discountedPrice),
    imageSrc: row.imageUrl ?? '',
    imageAlt: row.title,
    windowEnd: row.windowEnd,
    dealType: row.dealType,
    discountPercent: row.discountPercent ?? 0,
    stockRemaining: row.stockRemaining,
    stockTotal: row.stockTotal,
    isHotDeal: row.isHotDeal,
    soldCount: row.soldCount,
    vendorId: row.vendorId,
    minPrice: row.minPrice ?? null,
    maxPrice: row.maxPrice ?? null,
    maxDiscountPercent: row.maxDiscountPercent ?? null,
    axesCount: row.axesCount ?? 0,
    defaultSkuId: row.defaultSkuId ?? null,
    qtyTierTop:
      typeof row.qtyTierTop === 'string' ? JSON.parse(row.qtyTierTop) : (row.qtyTierTop ?? null),
    skuCount: row.skuCount ?? 1,
    heSlug: row.heSlug ?? undefined,
  };
}

function SkeletonGrid({ count }: { count: number }) {
  return (
    <>
      {Array.from({ length: count }, (_, i) => (
        <BrowseCardSkeleton key={i} />
      ))}
    </>
  );
}

function DealsBrowserInner({
  initialDeals,
  initialTotal,
  initialUrlState,
  priceCeiling: priceCeilingProp = 2000,
  priceFloor: priceFloorProp = 0,
  codecKind,
  apiPreset,
  defaultSort,
  emptyState,
  gridLabel,
}: Omit<DealsBrowserIslandProps, 'locale' | 'dehydratedState'>) {
  const priceCeiling = priceCeilingProp ?? 2000;
  const priceFloor = priceFloorProp ?? 0;
  const t = useT('deals_browse');
  const tDealType = useT('domain_deal_type');
  const { locale } = useLocale();
  const dir: 'rtl' | 'ltr' = dirForLocale(locale);

  const codec = useMemo(
    () => CODEC_FACTORIES[codecKind ?? 'deals']({ locale, priceCeiling }),
    [codecKind, locale, priceCeiling],
  );
  const { state, setUrlState } = useUrlFilterState({
    initial: (initialUrlState ?? {
      type: undefined,
      catSlug: undefined,
      tagSlugs: [],
      page: 1,
      sort: defaultSort ?? 'hot',
      priceMin: 0,
      priceMax: 0,
    }) as DealsUrlState & Record<string, unknown>,
    codec: codec as unknown as UrlCodec<DealsUrlState & Record<string, unknown>>,
  }) as UseUrlFilterStateReturn<DealsUrlState>;

  const facetPriceMin = state.priceMin > priceFloor ? state.priceMin : undefined;
  const facetPriceMax =
    state.priceMax > 0 && state.priceMax < priceCeiling ? state.priceMax : undefined;
  const queryType: '' | DealType = state.type ?? '';

  const queryParams = {
    type: queryType,
    catSlug: state.catSlug,
    tagSlugs: state.tagSlugs,
    sort: state.sort,
    page: state.page,
    priceMin: facetPriceMin,
    priceMax: facetPriceMax,
  };
  const browseQueryKey = {
    ...queryParams,
    ...(apiPreset ? { preset: apiPreset } : {}),
  };

  const { data, isFetching, isError } = useQuery<BrowseApiResponse>({
    queryKey: ['deals-browse', browseQueryKey],
    queryFn: () => fetchBrowse({ ...queryParams, preset: apiPreset }),
    initialData: {
      ok: true,
      results: initialDeals as DealBrowseRow[],
      total: initialTotal,
      page: initialUrlState?.page ?? 1,
      totalPages: Math.ceil(initialTotal / PAGE_SIZE),
    },
    initialDataUpdatedAt: 0,
    staleTime: 60_000,
    refetchOnWindowFocus: false,
  });

  const deals = data?.results ?? [];
  const totalPages = data?.totalPages ?? 1;

  const { data: facets } = useQuery<DealsFacetCounts>({
    queryKey: buildFacetKey({
      catSlug: state.catSlug,
      type: queryType,
      tagSlugs: state.tagSlugs,
      priceMin: facetPriceMin,
      priceMax: facetPriceMax,
      preset: apiPreset,
    }),
    queryFn: () =>
      fetchFacets({
        catSlug: state.catSlug,
        type: queryType,
        tagSlugs: state.tagSlugs,
        priceMin: facetPriceMin,
        priceMax: facetPriceMax,
        priceFloor,
        priceCeiling,
        preset: apiPreset,
      }),
    staleTime: 60_000,
    placeholderData: keepPreviousData,
    refetchOnWindowFocus: false,
  });

  const categories = facets?.categories ?? [];
  const tags = (facets?.tags ?? []).map((tag) => ({
    key: tag.slug,
    name: pickLocalized(tag, locale),
    count: tag.usageCount,
  }));
  const typeCounts = facets?.types;
  const skeletonCount = isFetching ? Math.max(0, PAGE_SIZE - initialDeals.length) : 0;

  const handlePageChange = useCallback(
    (next: number) => {
      setUrlState({ page: next }, HISTORY.tweak);
      window.scrollTo({ top: 0, behavior: 'smooth' });
    },
    [setUrlState],
  );

  const activeChips: ActiveChip[] = [
    state.type
      ? {
          label: tDealType(dealTypeMeta(state.type).labelKey),
          onRemove: () => setUrlState({ type: undefined, page: 1 }, HISTORY.tweak),
        }
      : null,
    state.catSlug
      ? {
          label:
            categories.find((category) => category.slug === state.catSlug)?.nameHe ?? state.catSlug,
          onRemove: () => setUrlState({ catSlug: undefined, tagSlugs: [], page: 1 }, HISTORY.tweak),
        }
      : null,
    ...state.tagSlugs.map((slug) => ({
      label: tags.find((tag) => tag.key === slug)?.name ?? slug,
      onRemove: () =>
        setUrlState(
          { tagSlugs: state.tagSlugs.filter((selectedSlug) => selectedSlug !== slug), page: 1 },
          HISTORY.tweak,
        ),
    })),
    facetPriceMin != null || facetPriceMax != null
      ? {
          label: `${formatAgorotWhole((facetPriceMin ?? priceFloor) * 100)}–${formatAgorotWhole((facetPriceMax ?? priceCeiling) * 100)}`,
          onRemove: () => setUrlState({ priceMin: 0, priceMax: 0, page: 1 }, HISTORY.tweak),
        }
      : null,
  ].filter(Boolean) as ActiveChip[];

  const sortOptions = [
    { key: 'hot' as BrowseSort, label: t('sort_hot') },
    { key: 'newest' as BrowseSort, label: t('sort_newest') },
    { key: 'biggest-discount' as BrowseSort, label: t('sort_biggest_discount') },
    { key: 'ending-soon' as BrowseSort, label: t('sort_ending_soon') },
  ];

  return (
    <div className="flex items-start gap-6 px-6">
      <BrowseFilterSidebar
        activeChips={activeChips}
        onClearAll={() =>
          setUrlState(
            {
              type: undefined,
              catSlug: undefined,
              tagSlugs: [],
              priceMin: 0,
              priceMax: 0,
              sort: defaultSort ?? 'hot',
              page: 1,
            },
            HISTORY.nav,
          )
        }
      >
        <DealTypeFacet
          label={t('filter_section_type')}
          selected={state.type ? [state.type] : []}
          onChange={(types) =>
            setUrlState({ type: types.length === 1 ? types[0]! : undefined, page: 1 }, HISTORY.nav)
          }
          showCounts
          counts={typeCounts}
          countPlacement="inline"
        />
        <CategoryFacet
          label={t('filter_category_label')}
          categories={categories.map(
            (category): CategoryOption => ({
              id: category.slug,
              slug: category.slug,
              nameHe: category.nameHe,
              nameEn: category.nameEn,
              dealCount: category.dealCount,
            }),
          )}
          selectedIds={state.catSlug ? [state.catSlug] : []}
          onToggle={(slug) =>
            setUrlState(
              {
                catSlug: state.catSlug === slug ? undefined : slug,
                tagSlugs: [],
                page: 1,
              },
              HISTORY.nav,
            )
          }
          allLabel={t('filter_category_all')}
          onSelectAll={() =>
            setUrlState({ catSlug: undefined, tagSlugs: [], page: 1 }, HISTORY.nav)
          }
          locale={dir === 'rtl' ? 'he' : 'en'}
          showCounts={true}
          countPlacement="inline"
        />
        <TagFacet
          label={t('filter_tags_label')}
          tags={tags}
          selected={state.tagSlugs}
          onToggle={(slug) => {
            const next = state.tagSlugs.includes(slug)
              ? state.tagSlugs.filter((selectedSlug) => selectedSlug !== slug)
              : [...state.tagSlugs, slug];
            setUrlState({ tagSlugs: 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(
              {
                priceMin: lo > priceFloor ? lo : 0,
                priceMax: hi < priceCeiling ? hi : 0,
                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>

      <div className="min-w-0 flex-1">
        <div className="mb-4 flex items-center gap-3">
          <SortDropdown
            value={state.sort}
            label={t('filter_sort_label')}
            options={sortOptions}
            locale={dir === 'rtl' ? 'he' : 'en'}
            onChange={(sort) => setUrlState({ sort, page: 1 }, HISTORY.tweak)}
          />
        </div>

        <div aria-live="polite" aria-busy={isFetching} aria-label={gridLabel ?? t('grid_label')}>
          {isError ? (
            <div
              role="alert"
              className="flex flex-col items-center justify-center py-16 text-center"
            >
              <p className="text-text-muted text-lg">{t('empty_state')}</p>
            </div>
          ) : deals.length === 0 && !isFetching ? (
            <div className="flex flex-col items-center justify-center py-16 text-center">
              <p className="text-text-muted text-lg">{emptyState ?? t('empty_state')}</p>
            </div>
          ) : (
            <div
              className="grid gap-4"
              style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(min(16rem, 100%), 1fr))' }}
            >
              {deals.map((deal, index) => (
                <DealCard
                  key={deal.id}
                  deal={browseRowToDealCardDeal(deal)}
                  aboveFold={index < 6}
                />
              ))}
              {skeletonCount > 0 && <SkeletonGrid count={skeletonCount} />}
            </div>
          )}
        </div>

        <Pagination
          page={state.page}
          totalPages={totalPages}
          onPageChange={handlePageChange}
          isLoading={isFetching}
        />
      </div>
    </div>
  );
}

export function DealsBrowserIsland(props: DealsBrowserIslandProps) {
  return (
    <HydratedIsland locale={props.locale} dehydratedState={props.dehydratedState}>
      <DealsBrowserInner {...props} />
    </HydratedIsland>
  );
}
