'use client';

/**
 * useCachedFilterData — hydration-safe hook for search filter data.
 *
 * React #418 safety: NO localStorage read in useState initializer or render path.
 * All LS access is inside useEffect (post-mount only).
 *
 * Strategy:
 *  1. SSR seed: first render uses `initial` prop (server-fetched).
 *  2. Post-mount: read LS only to backfill a degraded SSR seed (empty cat/tags).
 *     Server-derived bounds (priceFloor/priceCeiling/discountRange) NEVER come from LS.
 *  3. react-query owns background revalidation; when query data arrives,
 *     sync to state + write LS.
 *
 * `FilterData` is re-exported from the server query module to ensure the
 * type is identical on both the SSR path (search.astro) and this client hook.
 */

import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import type { Locale } from '@/lib/i18n';
import type { FilterData } from '@/server/db/queries/filter-data';
import { captureCaught } from '@/lib/observability';

export type { FilterData };

// ─── TTL constants ────────────────────────────────────────────────────────────

const CAT_TAG_TTL_MS = 24 * 60 * 60 * 1000; // 24 h

// ─── LS helpers ──────────────────────────────────────────────────────────────

function lsKey(kind: 'catTag' | 'other', locale: Locale) {
  return `md:search:filters:${kind}:v1:${locale}`;
}

function readLS<T>(key: string): { v: 1; fetchedAt: number; data: T } | null {
  if (typeof window === 'undefined') return null;
  try {
    const raw = window.localStorage.getItem(key);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as { v?: number; fetchedAt?: number; data?: unknown };
    if (parsed.v !== 1 || typeof parsed.fetchedAt !== 'number' || !parsed.data) return null;
    return { v: 1, fetchedAt: parsed.fetchedAt, data: parsed.data as T };
  } catch (err) {
    captureCaught(err, { scope: 'features.search.useCachedFilterData.readLS', severity: 'info' });
    return null;
  }
}

function writeLS<T>(key: string, data: T): void {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.setItem(key, JSON.stringify({ v: 1, fetchedAt: Date.now(), data }));
  } catch (err) {
    captureCaught(err, { scope: 'features.search.useCachedFilterData.writeLS', severity: 'info' });
  }
}

// ─── Merge policy ─────────────────────────────────────────────────────────────
//
// FilterData is server-derived facet metadata. User filter VALUES (priceMin/priceMax,
// selected category/tag, sort) live in URL state — not here.
//
// Always fresh (SSR seed / react-query — never shadowed by stale LS):
//   otherFilters.priceFloor, otherFilters.priceCeiling, otherFilters.discountRange
//   categories + topTags when SSR seed is non-degraded
//
// Restored from LS cache:
//   categories + topTags ONLY when SSR seed is degraded (both empty arrays).

function isDegradedSeed(data: FilterData): boolean {
  return data.categories.length === 0 && data.topTags.length === 0;
}

/** Pure merge for unit tests and post-mount LS sync. */
export function mergeFilterDataFromLSCache(
  fresh: FilterData,
  catTag: { fetchedAt: number; data: Pick<FilterData, 'categories' | 'topTags'> } | null,
  _other: { fetchedAt: number; data: FilterData['otherFilters'] } | null,
  now: number,
): FilterData {
  let next = fresh;

  if (catTag && now - catTag.fetchedAt < CAT_TAG_TTL_MS && isDegradedSeed(next)) {
    next = { ...next, categories: catTag.data.categories, topTags: catTag.data.topTags };
  }

  return next;
}

// ─── Hook ─────────────────────────────────────────────────────────────────────

export function useCachedFilterData(initial: FilterData, locale: Locale): FilterData {
  // SSR-safe seed: only `initial` used during first render.
  // No LS access here — would cause React #418 hydration mismatch.
  const [state, setState] = useState<FilterData>(initial);

  const { data: queryData } = useQuery<FilterData>({
    queryKey: ['filterData', locale],
    queryFn: async () => {
      const r = await fetch(`/api/deals/filter-data?locale=${locale}`);
      const j = (await r.json()) as { ok: boolean; data?: FilterData };
      if (!j.ok || !j.data) throw new Error('filter-data fetch failed');
      return j.data;
    },
    staleTime: 5 * 60_000,
    gcTime: 30 * 60_000,
    refetchOnMount: false,
  });

  // Post-mount: optional LS backfill for degraded SSR seed only.
  // Server-derived bounds always remain from `initial` / react-query — never from LS.
  useEffect(() => {
    const catTag = readLS<Pick<FilterData, 'categories' | 'topTags'>>(lsKey('catTag', locale));
    const other = readLS<FilterData['otherFilters']>(lsKey('other', locale));

    const timer = setTimeout(() => {
      setState((prev) => mergeFilterDataFromLSCache(prev, catTag, other, Date.now()));
    }, 0);
    return () => clearTimeout(timer);
  }, [locale]);

  // Sync revalidated query data → state + LS.
  // External-system sync per react-query revalidation; setState is required to push
  // the freshly fetched data into the consumer-visible state.
  useEffect(() => {
    if (!queryData) return;

    const timer = setTimeout(
      () =>
        setState((prev: FilterData) => {
          const catTagDifferent =
            JSON.stringify(prev.categories) !== JSON.stringify(queryData.categories) ||
            JSON.stringify(prev.topTags) !== JSON.stringify(queryData.topTags);
          const otherDifferent =
            JSON.stringify(prev.otherFilters) !== JSON.stringify(queryData.otherFilters);
          if (catTagDifferent) {
            writeLS(lsKey('catTag', locale), {
              categories: queryData.categories,
              topTags: queryData.topTags,
            });
          }
          if (otherDifferent) {
            writeLS(lsKey('other', locale), queryData.otherFilters);
          }
          if (!catTagDifferent && !otherDifferent) return prev;
          return queryData;
        }),
      0,
    );
    return () => clearTimeout(timer);
  }, [queryData, locale]);

  return state;
}
