/**
 * Schema Field Renderer
 *
 * Main component that determines which field component to render based on the
 * action schema field definition. Acts as a factory for field components.
 *
 * Supports conditional visibility via the `visible_when` field property.
 */

import { StringField } from './StringField';
import { NumberField } from './NumberField';
import { BooleanField } from './BooleanField';
import { SelectField } from './SelectField';
import { DynamicSelectField } from './DynamicSelectField';
import { KeyValueField } from './KeyValueField';
import { RepeatableFieldGroup } from './RepeatableFieldGroup';
import { FilterGroupFieldComponent } from './FilterGroupFieldComponent';
import { RichTextField } from './RichTextField';
import { ButtonGroupField } from './ButtonGroupField';
import { MultiSelectField } from './MultiSelectField';
import { DateField } from './DateField';
import { CombinedSelectorField } from '../CombinedSelectorField';
import { ActionField, Action } from '../../types/action';
import { AvailableContext } from '../../hooks/useAvailableContext';
import {
  isFieldVisible,
  FilterGroupValue,
  FieldChangeHandler,
  QueueFetchHandler,
} from '../../types/schema';

interface SchemaFieldRendererProps {
  fieldName: string;
  field: ActionField;
  value: unknown;
  onChange: FieldChangeHandler;
  availableContext: AvailableContext;
  validationError?: string;
  // Dynamic options state
  dynamicOptions: Record<string, unknown[]>;
  loadingDynamicFields: Record<string, boolean>;
  dynamicFieldErrors: Record<string, string>;
  // For fetching dynamic options
  onQueueFetch: QueueFetchHandler;
  // Current action config for dependency checking
  actionConfig: Record<string, unknown>;
  // Full action schema for combined selector fields
  currentAction?: Action;
}

export function SchemaFieldRenderer({
  fieldName,
  field,
  value,
  onChange,
  availableContext,
  validationError,
  dynamicOptions,
  loadingDynamicFields,
  dynamicFieldErrors,
  onQueueFetch,
  actionConfig,
  currentAction,
}: SchemaFieldRendererProps) {
  // Check conditional visibility - hide field if condition not met
  // Cast to schema field type for visibility check
  const schemaField = field as Parameters<typeof isFieldVisible>[0];
  if (!isFieldVisible(schemaField, actionConfig)) {
    return null;
  }

  // Handle combined selector fields (selector_for property)
  if (field.selector_for && currentAction?.schema[field.selector_for]) {
    const targetField = currentAction.schema[field.selector_for];
    const targetFieldName = field.selector_for;
    const targetValue = (actionConfig[targetFieldName] as string) || '';
    const targetError = validationError;

    // Get dependency info
    const dependsOnValue = field.depends_on ? actionConfig[field.depends_on] : null;
    const hasDependency = !!field.depends_on;
    const dependencyMet = !hasDependency || !!dependsOnValue;
    const dependencyLabel = field.depends_on
      ? currentAction.schema[field.depends_on]?.label
        || (field.depends_on === 'connection_id' ? 'an account' : field.depends_on.replace('_', ' '))
      : undefined;

    // Use cache key that includes dependency value for dependent fields
    const cacheKey = dependsOnValue ? `${fieldName}_${dependsOnValue}` : fieldName;
    const isLoading = loadingDynamicFields[cacheKey];
    const options = (dynamicOptions[cacheKey] as Array<Record<string, string>>) || [];
    const fieldError = dynamicFieldErrors[cacheKey];

    // Queue fetch when dependency met and field is dynamic
    // Always pass actionConfig so connection_id and other placeholders can be resolved
    if (
      field.dynamic &&
      field.endpoint &&
      dependencyMet &&
      !options.length &&
      !isLoading &&
      !fieldError
    ) {
      onQueueFetch(
        fieldName,
        field.endpoint,
        hasDependency && dependsOnValue ? String(dependsOnValue) : undefined,
        actionConfig,
      );
    }

    // Format options for CombinedSelectorField
    const formattedOptions = options.map((option) => ({
      value: String(field.value_field ? option[field.value_field] : option.value),
      label: field.label_field ? option[field.label_field] : option.label,
      breadcrumb: option.breadcrumb,
      description: option.description,
      icon: option.icon,
      disabled: String(option.disabled) === 'true' ? true : undefined,
      group: option.group,
    }));

    return (
      <CombinedSelectorField
        key={fieldName}
        selectorField={field}
        targetField={targetField}
        selectorFieldName={fieldName}
        targetFieldName={targetFieldName}
        targetValue={targetValue}
        options={formattedOptions}
        isLoading={isLoading}
        error={fieldError}
        dependencyMet={dependencyMet}
        dependencyLabel={dependencyLabel}
        onChange={onChange}
        availableContext={availableContext}
        validationError={targetError}
      />
    );
  }

  // Handle dynamic select fields
  if (field.type === 'select' && field.dynamic && field.endpoint) {
    const dependsOnValue = field.depends_on ? actionConfig[field.depends_on] : null;
    const hasDependency = !!field.depends_on;
    const dependencyMet = !hasDependency || !!dependsOnValue;
    const cacheKey = dependsOnValue ? `${fieldName}_${dependsOnValue}` : fieldName;
    const isLoading = loadingDynamicFields[cacheKey];
    const options = (dynamicOptions[cacheKey] as Array<Record<string, string>>) || [];
    const fieldError = dynamicFieldErrors[cacheKey];

    // Queue fetch when appropriate
    // Always pass actionConfig so connection_id and other placeholders can be resolved
    if (dependencyMet && !options.length && !isLoading && !fieldError) {
      onQueueFetch(
        fieldName,
        field.endpoint,
        hasDependency && dependsOnValue ? String(dependsOnValue) : undefined,
        actionConfig,
      );
    }

    const formattedOptions = options.map((option) => ({
      value: String(field.value_field ? option[field.value_field] : option.value),
      label: field.label_field ? option[field.label_field] : option.label,
      description: option.description,
      icon: option.icon,
      disabled: String(option.disabled) === 'true' ? true : undefined,
      group: option.group,
    }));

    return (
      <DynamicSelectField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        options={formattedOptions}
        isLoading={isLoading}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
        fieldError={fieldError}
        dependencyMet={dependencyMet}
        dependencyLabel={
          field.depends_on
            ? currentAction?.schema[field.depends_on]?.label
              || (field.depends_on === 'connection_id' ? 'an account' : field.depends_on.replace('_', ' '))
            : undefined
        }
        availableContext={availableContext}
      />
    );
  }

  // Handle dynamic multi_select fields
  if (field.type === 'multi_select' && field.dynamic && field.endpoint) {
    const dependsOnValue = field.depends_on ? actionConfig[field.depends_on] : null;
    const hasDependency = !!field.depends_on;
    const dependencyMet = !hasDependency || !!dependsOnValue;
    const cacheKey = dependsOnValue ? `${fieldName}_${dependsOnValue}` : fieldName;
    const isLoading = loadingDynamicFields[cacheKey];
    const options = (dynamicOptions[cacheKey] as Array<Record<string, string>>) || [];
    const fieldError = dynamicFieldErrors[cacheKey];

    if (dependencyMet && !options.length && !isLoading && !fieldError) {
      onQueueFetch(
        fieldName,
        field.endpoint,
        hasDependency && dependsOnValue ? String(dependsOnValue) : undefined,
        actionConfig,
      );
    }

    const formattedOptions = options.map((option) => ({
      value: String(field.value_field ? option[field.value_field] : option.value),
      label: field.label_field ? option[field.label_field] : option.label,
      description: option.description,
      icon: option.icon,
      disabled: String(option.disabled) === 'true' ? true : undefined,
      group: option.group,
    }));

    return (
      <MultiSelectField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string[]) || []}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
        options={formattedOptions}
        isLoading={isLoading}
        fieldError={fieldError}
        dependencyMet={dependencyMet}
        dependencyLabel={
          field.depends_on
            ? currentAction?.schema[field.depends_on]?.label
              || (field.depends_on === 'connection_id' ? 'an account' : field.depends_on.replace('_', ' '))
            : undefined
        }
      />
    );
  }

  // Handle key_value fields
  if (field.type === 'key_value') {
    const dependsOnValue = field.depends_on ? actionConfig[field.depends_on] : null;
    const cacheKey = dependsOnValue ? `${fieldName}_${dependsOnValue}` : fieldName;
    const availableKeys = (dynamicOptions[cacheKey] as Array<Record<string, string>>) || [];
    const isLoading = loadingDynamicFields[cacheKey];
    const fieldError = dynamicFieldErrors[cacheKey];

    // Queue fetch when dependency value changes
    if (
      field.dynamic_options &&
      field.endpoint &&
      dependsOnValue &&
      !availableKeys.length &&
      !isLoading &&
      !fieldError
    ) {
      onQueueFetch(fieldName, field.endpoint, String(dependsOnValue), actionConfig);
    }

    return (
      <KeyValueField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as Record<string, string>) || {}}
        availableKeys={availableKeys}
        isLoading={isLoading}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
        fieldError={fieldError}
        dependencyMet={!!dependsOnValue || !field.depends_on}
        dependencyLabel={
          field.depends_on
            ? currentAction?.schema[field.depends_on]?.label
              || (field.depends_on === 'connection_id' ? 'an account' : field.depends_on.replace('_', ' '))
            : undefined
        }
      />
    );
  }

  // Handle string and text fields
  if (field.type === 'string' || field.type === 'text') {
    return (
      <StringField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
      />
    );
  }

  // Handle date fields
  if (field.type === 'date') {
    return (
      <DateField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
      />
    );
  }

  // Handle richtext fields
  if (field.type === 'richtext') {
    return (
      <RichTextField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
      />
    );
  }

  // Handle number fields
  if (field.type === 'number') {
    return (
      <NumberField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as number | string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
      />
    );
  }

  // Handle boolean fields
  if (field.type === 'boolean') {
    return (
      <BooleanField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={Boolean(value)}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
      />
    );
  }

  // Handle static select fields
  if (field.type === 'select' && !field.dynamic && field.options) {
    return (
      <SelectField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
      />
    );
  }

  // Handle button_group fields
  if (field.type === 'button_group' && field.options) {
    return (
      <ButtonGroupField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string) || ''}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
      />
    );
  }

  // Handle static multi_select fields
  if (field.type === 'multi_select' && !field.dynamic && field.options) {
    return (
      <MultiSelectField
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as string[]) || []}
        onChange={(val) => onChange(fieldName, val)}
        error={validationError}
      />
    );
  }

  // Handle repeatable field groups
  if (field.type === 'repeatable' && field.item_schema) {
    return (
      <RepeatableFieldGroup
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={(value as Record<string, unknown>[]) || []}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        error={validationError}
        dynamicOptions={dynamicOptions}
        loadingDynamicFields={loadingDynamicFields}
        dynamicFieldErrors={dynamicFieldErrors}
        onQueueFetch={onQueueFetch}
        actionConfig={actionConfig}
        currentAction={currentAction}
      />
    );
  }

  // Handle filter group fields
  if (field.type === 'filter_group' && field.filter_config) {
    // When no external property source or endpoint, derive properties from available context.
    // This supports the Router action where conditions filter on preceding node outputs.
    if (!field.filter_config.property_source && !field.filter_config.property_endpoint) {
      const contextProperties: Array<Record<string, string>> = availableContext.groups.flatMap(
        (group) =>
          group.variables.map((v) => ({
            id: v.path,
            name: `${group.sourceName}: ${v.label}`,
            type: v.type,
          })),
      );

      return (
        <FilterGroupFieldComponent
          key={fieldName}
          fieldName={fieldName}
          field={field}
          value={value as FilterGroupValue | undefined}
          onChange={(val) => onChange(fieldName, val)}
          availableContext={availableContext}
          properties={contextProperties}
          error={validationError}
        />
      );
    }

    const propertySourceValue = field.filter_config.property_source
      ? actionConfig[field.filter_config.property_source]
      : null;

    // Fetch properties if needed
    const propertyCacheKey = propertySourceValue
      ? `${fieldName}_properties_${propertySourceValue}`
      : `${fieldName}_properties`;
    const properties = (dynamicOptions[propertyCacheKey] as Array<Record<string, string>>) || [];
    const isLoadingProperties = loadingDynamicFields[propertyCacheKey];
    const propertyError = dynamicFieldErrors[propertyCacheKey];

    // Queue property fetch if needed
    if (
      field.filter_config.property_endpoint &&
      propertySourceValue &&
      !properties.length &&
      !isLoadingProperties &&
      !propertyError
    ) {
      onQueueFetch(
        `${fieldName}_properties`,
        field.filter_config.property_endpoint,
        String(propertySourceValue),
        actionConfig,
      );
    }

    return (
      <FilterGroupFieldComponent
        key={fieldName}
        fieldName={fieldName}
        field={field}
        value={value as FilterGroupValue | undefined}
        onChange={(val) => onChange(fieldName, val)}
        availableContext={availableContext}
        properties={properties}
        isLoadingProperties={isLoadingProperties}
        propertyError={propertyError}
        error={validationError}
      />
    );
  }

  // Fallback: render nothing for unsupported field types
  return null;
}
