// seekora-ui-sdk/packages/react/src/layout/LayoutRenderer.tsx
import * as React from "react";
import { validateLayoutConfig } from "./validate-config";
import { resolveProvider } from "./provider-resolver";
import { ThemeInjector } from "./theme-injector";
import { renderSurfaceShell } from "./render-surface-shell";
import { resolvePageSentinels } from "./page-sentinels";
import { DrawerProvider, ThemeHarvestContext, } from "./context-wired-props";
import { useSearchState } from "../hooks/useSearchState";
import { useSearchContext } from "../components/SearchProvider";
import { SeekoraErrorBoundary } from "../components/ErrorBoundary";
// SearchStateManager seeds state.query from initialQuery but never fires
// a search on construction (autoSearch only triggers on setQuery calls).
// This child component fires search() once after mount so the initial
// `?q=<url>` actually fetches results. SCRUM-268.
function InitialSearchTrigger() {
    const { search, results } = useSearchState();
    React.useEffect(() => {
        // Skip if results already populated (e.g. constructor eagerly consumed
        // initialResultsPromise). Avoids redundant network search on mount.
        if (!results) {
            void search();
        }
        // Run once on mount.
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    return null;
}
// Fires onReady exactly once after the first render that has search results
// populated. Storefront v4.js uses this to delay revealing the page (lifting
// anti-flash CSS) until Seekora content is actually painted — prevents the
// flash where theme grid shows between mount and first results render.
function ReadyNotifier({ onReady }) {
    const { results, loading } = useSearchState();
    const calledRef = React.useRef(false);
    React.useEffect(() => {
        if (calledRef.current)
            return;
        if (results && !loading) {
            calledRef.current = true;
            onReady();
        }
    }, [results, loading, onReady]);
    return null;
}
// Calls onStateManagerReady once after the SearchProvider mounts, giving
// external code (e.g. v4.js) a handle to the SearchStateManager instance so
// it can wire the theme input value to Seekora's search state. SCRUM-268.
function StateManagerReadyNotifier({ onReady, }) {
    const { stateManager } = useSearchContext();
    const calledRef = React.useRef(false);
    React.useEffect(() => {
        if (calledRef.current)
            return;
        calledRef.current = true;
        onReady(stateManager);
        // stateManager identity is stable for the lifetime of the SearchProvider
        // instance; no need to re-run when it changes (would only happen on a full
        // provider remount, which creates a new LayoutRenderer tree anyway).
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    return null;
}
export function LayoutRenderer({ config, client, pageContext, onStateManagerReady, initialResultsPromise, initialQuery, trackContext, onReady, themeHarvest, }) {
    validateLayoutConfig(config);
    // Memoize derived props so their identities stay stable across re-renders.
    // SearchProvider's `useMemo` for the inner SearchStateManager has these as
    // dependencies — a fresh object reference every render would re-create the
    // state manager, reset state, re-fire search → infinite loop. SCRUM-268.
    const providerProps = React.useMemo(() => resolvePageSentinels(config.providerProps, pageContext ?? {}), [config.providerProps, pageContext]);
    // Force-disable the legacy V3 trackSearch path inside SearchStateManager
    // (state-manager.ts calls `client.trackSearch(...)` after every search
    // unless `disableLegacyTrackSearch` is set). Merge into any caller-supplied
    // stateManager config. SCRUM-268.
    const mergedStateManager = React.useMemo(() => ({
        disableLegacyTrackSearch: true,
        ...(providerProps.stateManager ?? {}),
    }), [providerProps]);
    // Disable the SDK's legacy `client.trackEvents()` auto-fire path. That
    // path POSTs to `/api/analytics/batch` and 401s on storefront credentials
    // (it expected an admin/JWT context that storefronts never have). All
    // storefront analytics events must flow through analytics-next → Jitsu
    // Ingest, initialised separately in the storefront bootstrap. Consumers
    // can override by passing `autoTrackSearch: true` in providerProps.
    // SCRUM-268 (legacy-analytics-disable).
    const outerProps = React.useMemo(() => ({
        client,
        enableAnalytics: false,
        autoTrackSearch: false,
        ...providerProps,
        stateManager: mergedStateManager,
        // Thread server-rendered boot results through to SearchProvider so
        // the first search() call skips a network round-trip. SCRUM-268.
        ...(initialResultsPromise != null
            ? { initialResultsPromise, initialQuery: initialQuery ?? "" }
            : {}),
        // V4 analytics emission context — passed through from mountLayout
        // caller so ui-sdk descendants emit Object Clicked / Search
        // Impression / Facet Applied via the consumer's analytics-next.
        // SCRUM-259.
        ...(trackContext ? { trackContext } : {}),
    }), [
        client,
        providerProps,
        mergedStateManager,
        initialResultsPromise,
        initialQuery,
        trackContext,
    ]);
    const { outer: Outer, inner: Inner } = resolveProvider(config.surface);
    const content = (React.createElement(SeekoraErrorBoundary, { surface: config.surface },
        React.createElement(React.Fragment, null,
            React.createElement(ThemeInjector, { theme: config.theme, surface: config.surface }),
            React.createElement(InitialSearchTrigger, null),
            onStateManagerReady && (React.createElement(StateManagerReadyNotifier, { onReady: onStateManagerReady })),
            onReady && React.createElement(ReadyNotifier, { onReady: onReady }),
            React.createElement(ThemeHarvestContext.Provider, { value: themeHarvest ?? null },
                React.createElement(DrawerProvider, null,
                    React.createElement("div", { className: `seekora-layout seekora-layout--${config.layout}`, "data-seekora-surface": config.surface }, renderSurfaceShell(config)))))));
    if (Inner) {
        const innerProps = {
            client,
            enableAnalytics: false,
        };
        if (config.surface === "predictive") {
            innerProps.initialOpen = true;
            innerProps.minQueryLength = 0;
        }
        if (config.documentFieldMapping) {
            innerProps.documentFieldMapping = config.documentFieldMapping;
        }
        return (React.createElement(Outer, { ...outerProps },
            React.createElement(Inner, { ...innerProps }, content)));
    }
    return React.createElement(Outer, { ...outerProps }, content);
}
