import React from 'react';
import { useState, useMemo } from 'react';
import { FieldPath, FormGroup } from '../../form/form-groups';
import {
  ErrorRenderer,
  ErrorRendererContext,
  ErrorRendererProps,
  ErrorRendererReturn,
  FieldErrorRendererConfig,
} from './types';
import { useMemoizedCallback } from '../../../common/useMemoizedCallback';

/**
 * Default error renderer that returns a simple error message
 */
const defaultErrorRenderer = <T extends Record<string, any>>({
  error,
}: ErrorRendererProps<T>): React.ReactNode => {
  const errorMessage = Array.isArray(error) ? error[0] : error;
  return <div className="reform-error">{errorMessage}</div>;
};

/**
 * Hook for managing custom error renderers in Reform forms
 *
 * @template T - The type of form data
 * @param groups - The form groups
 * @returns Object with error renderer utilities
 *
 * @example
 * // Basic usage
 * const reform = useReform(config);
 * const { registerErrorRenderer, renderError } = useErrorRenderer(reform.getGroups());
 *
 * // Register a custom renderer for email fields
 * useEffect(() => {
 *   const unregister = registerErrorRenderer({
 *     field: 'email',
 *     renderer: ({ error }) => <EmailErrorMessage error={error} />
 *   });
 *
 *   return unregister;
 * }, []);
 *
 * // Use the renderer in your form
 * return (
 *   <div>
 *     <input {...reform.register(0, 'email')} />
 *     {renderError('email', reform.getFieldError(0, 'email'), 0)}
 *   </div>
 * );
 */
export const useErrorRenderer = <T extends Record<string, any>>(
  groups: FormGroup<T>[]
): ErrorRendererReturn<T> => {
  // State for the error renderer context
  const [context, setContext] = useState<ErrorRendererContext<T>>({
    defaultRenderer: defaultErrorRenderer,
    fieldRenderers: [],
    showAllErrors: false,
    contextData: {},
  });

  /**
   * Register a field-specific error renderer
   */
  const registerErrorRenderer = useMemoizedCallback(
    (config: FieldErrorRendererConfig<T>) => {
      const { field, renderer, priority = 0 } = config;

      // Add the new renderer to the list
      setContext(prev => {
        const newRenderers = [
          ...prev.fieldRenderers,
          { field, renderer, priority },
        ];

        // Sort by priority (higher numbers first)
        newRenderers.sort((a, b) => (b.priority || 0) - (a.priority || 0));

        return {
          ...prev,
          fieldRenderers: newRenderers,
        };
      });

      // Return a function to unregister the renderer
      return () => {
        setContext(prev => ({
          ...prev,
          fieldRenderers: prev.fieldRenderers.filter(r => r !== config),
        }));
      };
    },
    []
  );

  /**
   * Set the default error renderer
   */
  const setDefaultRenderer = useMemoizedCallback(
    (renderer: ErrorRenderer<T>) => {
      setContext(prev => ({
        ...prev,
        defaultRenderer: renderer,
      }));
    },
    []
  );

  /**
   * Check if a field matches a field pattern
   */
  const fieldMatches = useMemoizedCallback(
    (
      fieldPattern: FieldPath<T> | RegExp | ((field: FieldPath<T>) => boolean),
      field: FieldPath<T>
    ): boolean => {
      if (typeof fieldPattern === 'function') {
        return fieldPattern(field);
      }

      if (fieldPattern instanceof RegExp) {
        return fieldPattern.test(String(field));
      }

      return fieldPattern === field;
    },
    []
  );

  /**
   * Get the appropriate renderer for a field
   */
  const getRendererForField = useMemoizedCallback(
    (field: FieldPath<T>): ErrorRenderer<T> => {
      // Find the first matching renderer
      const matchingConfig = context.fieldRenderers.find(config =>
        fieldMatches(config.field, field)
      );

      return matchingConfig?.renderer || context.defaultRenderer;
    },
    [context.fieldRenderers, context.defaultRenderer, fieldMatches]
  );

  /**
   * Render an error for a specific field
   */
  const renderError = useMemoizedCallback(
    (
      field: FieldPath<T>,
      error: string | string[] | undefined,
      groupIndex: number
    ): React.ReactNode => {
      if (!error) return null;

      const renderer = getRendererForField(field);
      const value =
        groupIndex >= 0 && groupIndex < groups.length
          ? groups[groupIndex].data[field as keyof T]
          : undefined;

      const props: ErrorRendererProps<T> = {
        field,
        error,
        value,
        groupIndex,
        groups,
        meta: context.contextData,
      };

      return renderer(props);
    },
    [groups, context.contextData, getRendererForField]
  );

  /**
   * Set whether to show all errors or just the first one
   */
  const setShowAllErrors = useMemoizedCallback((showAll: boolean) => {
    setContext(prev => ({
      ...prev,
      showAllErrors: showAll,
    }));
  }, []);

  /**
   * Update the context data
   */
  const updateContextData = useMemoizedCallback((data: Record<string, any>) => {
    setContext(prev => ({
      ...prev,
      contextData: {
        ...prev.contextData,
        ...data,
      },
    }));
  }, []);

  // Return memoized object to prevent unnecessary re-renders
  return useMemo(
    () => ({
      registerErrorRenderer,
      setDefaultRenderer,
      renderError,
      getRendererForField,
      setShowAllErrors,
      updateContextData,
      context,
    }),
    [
      registerErrorRenderer,
      setDefaultRenderer,
      renderError,
      getRendererForField,
      setShowAllErrors,
      updateContextData,
      context,
    ]
  );
};
