/**
 * Component Registry Utility
 *
 * Creates a memoized component registry for mapping node types to React components.
 * Handles lazy loading, memoization caching, and fallback rendering.
 */

import React from 'react';

/**
 * A map of type strings to lazy-loaded React components
 */
export type ComponentMap<TProps> = {
	[type: string]: React.LazyExoticComponent<React.ComponentType<TProps>>;
};

/**
 * Options for creating a component registry
 */
export interface CreateComponentRegistryOptions<TProps extends object> {
	/**
	 * The component mapping from type string to lazy-loaded component
	 */
	registry: ComponentMap<TProps>;
	/**
	 * Optional fallback component for unknown types
	 */
	fallback?: React.ComponentType<TProps>;
	/**
	 * Optional function to generate display name for memoized components
	 * Default: `Memoized${type}Component`
	 */
	getDisplayName?: (type: string) => string;
}

/**
 * Result of createComponentRegistry
 */
export interface ComponentRegistry<TProps extends object> {
	/**
	 * Get a memoized component for a given type.
	 * Returns null if type is not found and no fallback is provided.
	 */
	getComponent: (type: string) => React.ComponentType<TProps> | null;
	/**
	 * Get all registered type keys
	 */
	getTypes: () => string[];
	/**
	 * Check if a type is registered
	 */
	hasType: (type: string) => boolean;
	/**
	 * The fallback component (if provided)
	 */
	Fallback: React.ComponentType<TProps> | null;
}

/**
 * Creates a component registry with automatic memoization and caching.
 *
 * @example
 * ```tsx
 * const { getComponent, Fallback } = createComponentRegistry({
 *   registry: {
 *     notes: React.lazy(() => import('./notes/mod')),
 *     player: React.lazy(() => import('./player/mod')),
 *   },
 *   fallback: ({ nodeData }) => <div>Unknown: {nodeData.type}</div>,
 * });
 *
 * // Usage
 * const Component = getComponent(node.type) || Fallback;
 * return <Component nodeData={node} />;
 * ```
 */
export function createComponentRegistry<TProps extends object>({
	registry,
	fallback,
	getDisplayName = (type) => `Memoized${type}Component`,
}: CreateComponentRegistryOptions<TProps>): ComponentRegistry<TProps> {
	// Cache for memoized component wrappers - one per type
	const memoizedCache = new Map<string, React.ComponentType<TProps>>();

	const getComponent = (type: string): React.ComponentType<TProps> | null => {
		// Check cache first
		if (memoizedCache.has(type)) {
			return memoizedCache.get(type)!;
		}

		const LazyComponent = registry[type];
		if (!LazyComponent) {
			return fallback || null;
		}

		// Create a stable wrapper component for this type
		function RegistryComponent(props: TProps) {
			return <LazyComponent {...props} />;
		}
		RegistryComponent.displayName = getDisplayName(type);

		// Cache it for future calls
		memoizedCache.set(type, RegistryComponent);

		return RegistryComponent;
	};

	const getTypes = (): string[] => Object.keys(registry);

	const hasType = (type: string): boolean => type in registry;

	return {
		getComponent,
		getTypes,
		hasType,
		Fallback: fallback || null,
	};
}

/**
 * Creates a simple fallback component that displays the unknown type
 */
export function createFallbackComponent<TProps extends { nodeData?: { dbData?: { node_type?: string } } }>(
	getMessage?: (props: TProps) => string,
): React.FC<TProps> {
	return function FallbackComponent(props: TProps) {
		const message = getMessage
			? getMessage(props)
			: `Unsupported type: "${props.nodeData?.dbData?.node_type || 'Unknown'}"`;

		return (
			<div style={{ padding: '10px', color: 'orange' }}>
				<p>{message}</p>
			</div>
		);
	};
}
