import { useEffect, useRef } from 'react'
import type { WidgetLoaderProps } from './types'
import { useWidgetStore, widgetStoreActions } from '../stores/widget-store'
import type { WrapperState } from '../wrapper'
/**
* Foundation component for all widgets that manages state initialization, synchronization with the widget store, and data transformation pipeline execution.
*
* @remarks
* The WidgetLoader is stateless -- it manages data flow but does not render any UI. Always wrap it with WidgetWrapper for consistent widget appearance.
*
* @example
* ```tsx
*
*
*
*
*
* ```
*/
export function WidgetLoader>(
props: WidgetLoaderProps,
) {
// Subscribe only to this widget's registeredTools via Zustand selector.
// The selector returns a stable reference when other widgets change,
// avoiding unnecessary re-evaluations of Effect 4.
const registeredTools = useWidgetStore(
(state) => state.widgets[props.id]?.registeredTools,
)
const dataRef = useRef(props.data)
const configRef = useRef(props.config)
const isMountedRef = useRef(false)
useEffect(() => {
dataRef.current = props.data
configRef.current = props.config
})
// Effect 1: Metadata — type, loading, and error states in a single setWidget call.
// Merged to reduce store updates from 2 to 1 per widget during initialization.
useEffect(() => {
widgetStoreActions.setWidget(props.id, {
type: props.type,
isLoading: props.isLoading ?? false,
isFetching: props.isFetching ?? false,
error: props.error,
})
}, [props.id, props.type, props.isLoading, props.isFetching, props.error])
// Effect 2: Config updates — run through config pipeline
useEffect(() => {
if (props.config) {
void widgetStoreActions.executeConfigPipeline(props.id, props.config)
}
}, [props.id, props.config])
// Effect 3: Execute tool pipeline when props.data changes
useEffect(() => {
void widgetStoreActions.executeToolPipeline(props.id, props.data)
}, [props.id, props.data])
// Effect 4: Re-execute pipelines when registered tools change.
// Uses requestAnimationFrame to coalesce rapid successive registeredTools
// changes (e.g., 6 action components each calling registerTool on mount)
// into a single pipeline execution instead of 6 pairs.
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true
return
}
const rafId = requestAnimationFrame(() => {
const { executeToolPipeline, executeConfigPipeline } = widgetStoreActions
void executeToolPipeline(props.id, dataRef.current)
if (configRef.current) {
void executeConfigPipeline(props.id, configRef.current)
}
})
return () => cancelAnimationFrame(rafId)
}, [registeredTools, props.id])
return props.children
}