/**
 * useFilterImpressionTracker — records `Filter Viewed` (V4 event 12) when a
 * facet GROUP container first crosses the visibility threshold.
 *
 * Sibling of `useImpressionTracker` (which fires `Search Impression` for
 * result cards). Where that hook tracks ONE element via a `RefObject`, a
 * facet panel renders MANY groups in a `.map(...)` — and you can't call a
 * fixed-arity hook once per dynamic group. So this hook owns a registry of
 * group containers keyed by `filter_attribute` and returns a ref-callback
 * factory `registerGroupRef(filter_attribute)` that `<Facets>` attaches to
 * each group's root element.
 *
 * Each registered element is observed exactly once via the framework-agnostic
 * `buildFilterImpressionEffect` builder (IntersectionObserver under the hood).
 * On first viewport entry it calls `trackFilterViewed(ctx, {filter_attribute,
 * surface, search_id})`. The builder's `observeOnce` guarantees one emission
 * per element, then disconnects. Observers are torn down on unmount.
 *
 * `search_id` is intentionally omitted — matching `SearchStateManager.
 * emitFacetApplied`, which leaves it out so the producer-side resolver picks
 * up the active search. This keeps `Filter Viewed` and `Facet Applied`
 * attributed to the SAME search session server-side via the shared resolver
 * chain (in-memory -> sessionStorage -> URL `?seekora_sid`).
 *
 * No-op safety: when `ctx` is null/undefined (no `trackContext` on
 * SearchProvider, SSR first paint, standalone UMD mount) the registered
 * ref-callbacks are inert — they never wire an observer and never emit. Same
 * bail-out contract as `useImpressionTracker`.
 *
 * Usage (inside <Facets>):
 *   const registerGroupRef = useFilterImpressionTracker({
 *     ctx: useTrackContext(),
 *     surface: "results_page",
 *   });
 *   // per group:
 *   <div ref={registerGroupRef(facet.field)}>...</div>
 *
 * SCRUM-259 Phase 4c — wires the highest-seam "Filter Viewed" producer that
 * shipped with `buildFilterImpressionEffect` but was never consumed.
 */
import { useCallback, useEffect, useRef } from "react";
import { buildFilterImpressionEffect, } from "@seekora-ai/ui-sdk-core";
export function useFilterImpressionTracker(props) {
    const { ctx, surface, tracker } = props;
    // Map of filter_attribute -> the active observer cleanup fn for the
    // currently-attached element. Lets us tear observers down on detach /
    // unmount and avoid double-observing the same group across re-renders.
    const cleanupsRef = useRef(new Map());
    // Map of filter_attribute -> the element currently attached for it, so a
    // re-render that hands us the SAME node doesn't re-observe it.
    const elementsRef = useRef(new Map());
    // Keep latest ctx/surface/tracker in a ref so the stable ref-callback
    // identity (below) always reads current values without re-subscribing.
    const latestRef = useRef({ ctx, surface, tracker });
    latestRef.current = { ctx, surface, tracker };
    // Disconnect every observer on unmount.
    useEffect(() => {
        const cleanups = cleanupsRef.current;
        const elements = elementsRef.current;
        return () => {
            cleanups.forEach((cleanup) => {
                try {
                    cleanup();
                }
                catch {
                    /* never let observer teardown throw */
                }
            });
            cleanups.clear();
            elements.clear();
        };
    }, []);
    // Stable factory — the returned ref callback identity is stable per
    // filter_attribute across renders so React doesn't detach/reattach on
    // every render (which would re-fire the observer wiring needlessly).
    const refCallbacks = useRef(new Map());
    return useCallback((filterAttribute) => {
        const existing = refCallbacks.current.get(filterAttribute);
        if (existing)
            return existing;
        const cb = (el) => {
            const cleanups = cleanupsRef.current;
            const elements = elementsRef.current;
            // Detach (React passes null when the node is removed or replaced).
            if (!el) {
                const prevCleanup = cleanups.get(filterAttribute);
                if (prevCleanup) {
                    try {
                        prevCleanup();
                    }
                    catch {
                        /* swallow */
                    }
                    cleanups.delete(filterAttribute);
                }
                elements.delete(filterAttribute);
                return;
            }
            // Same node already observed for this group — nothing to do.
            if (elements.get(filterAttribute) === el)
                return;
            // A different node replaced the previous one — tear the old observer
            // down before wiring the new element.
            const prevCleanup = cleanups.get(filterAttribute);
            if (prevCleanup) {
                try {
                    prevCleanup();
                }
                catch {
                    /* swallow */
                }
                cleanups.delete(filterAttribute);
            }
            elements.delete(filterAttribute);
            const { ctx: curCtx, surface: curSurface, tracker: curTracker, } = latestRef.current;
            // No analytics context -> no-op (same bail-out as useImpressionTracker).
            if (!curCtx)
                return;
            const attach = buildFilterImpressionEffect(curCtx, { filter_attribute: filterAttribute, surface: curSurface }, curTracker ? { tracker: curTracker } : undefined);
            const cleanup = attach(el);
            cleanups.set(filterAttribute, cleanup);
            elements.set(filterAttribute, el);
        };
        refCallbacks.current.set(filterAttribute, cb);
        return cb;
    }, []);
}
