import { useState, useEffect, useRef, useMemo } from 'react';
import type { FeatureMeta, FeatureFilters, Bounds } from '../types';
import { apiUrl, buildFilterString, logNonAbortError, authHeaders, isAbortError } from '../lib/api';
import type { TravelTimeEntry } from './useTravelTime';
import { buildTravelParam } from '../lib/travel-params';

const DEBOUNCE_MS = 400;

interface FilterCountsResponse {
  total: number;
  impacts: Record<string, number>;
}

/**
 * Single source of truth for the in-view property count. Returns:
 *  - `total`: exact count of in-bounds properties passing the active filters
 *    (or the full in-bounds total when there are none); null until first load.
 *  - `impacts`: per-filter rejection counts — how many in-bounds properties
 *    each active filter removes.
 */
export function useFilterCounts(
  filters: FeatureFilters,
  features: FeatureMeta[],
  bounds: Bounds | null,
  travelTimeEntries: TravelTimeEntry[],
  shareCode?: string
) {
  const [impacts, setImpacts] = useState<Record<string, number>>({});
  // null = not loaded yet for the current bounds (hide the counter until the
  // first response). The number is the exact count of in-bounds properties
  // passing the active filters — and, with no filters, the total in bounds.
  const [total, setTotal] = useState<number | null>(null);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const abortRef = useRef<AbortController | null>(null);

  const travelParam = useMemo(() => {
    return buildTravelParam(travelTimeEntries);
  }, [travelTimeEntries]);
  const requestIdRef = useRef(0);

  useEffect(() => {
    requestIdRef.current += 1;
    const requestId = requestIdRef.current;

    if (!bounds) {
      abortRef.current?.abort();
      setImpacts({});
      setTotal(null);
      return;
    }

    if (debounceRef.current) clearTimeout(debounceRef.current);

    debounceRef.current = setTimeout(async () => {
      if (abortRef.current) abortRef.current.abort();
      abortRef.current = new AbortController();

      try {
        const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
        const filtersStr = buildFilterString(filters, features);
        const params = new URLSearchParams({ bounds: boundsStr });
        if (filtersStr) params.set('filters', filtersStr);
        if (travelParam) params.set('travel', travelParam);
        if (shareCode) params.set('share', shareCode);

        const res = await fetch(
          apiUrl('filter-counts', params),
          authHeaders({ signal: abortRef.current!.signal })
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json: FilterCountsResponse = await res.json();
        if (requestIdRef.current !== requestId) return;
        setImpacts(json.impacts);
        setTotal(json.total);
      } catch (err) {
        if (!isAbortError(err)) {
          logNonAbortError('Failed to fetch filter counts', err);
        }
      }
    }, DEBOUNCE_MS);

    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current);
      abortRef.current?.abort();
    };
  }, [filters, features, bounds, travelParam, travelTimeEntries, shareCode]);

  // Cancel in-flight on unmount
  useEffect(() => {
    return () => {
      abortRef.current?.abort();
    };
  }, []);

  return { impacts, total };
}
