import { useState, useEffect, useCallback, useRef } from 'react';
import { FilterGroup } from '../types/filter';
import type { FieldChangeHandler } from '../types/schema';
import { useDebouncedCallback } from './useDebouncedCallback';

// Debounce delay for live updates (ms)
const DEBOUNCE_DELAY = 300;

/**
 * Simplified node representation for config management.
 */
export interface ConfigNode {
  id: string;
  type: 'trigger' | 'action' | 'operator';
  config?: Record<string, unknown>;
  label?: string;
  // Pass-through properties for update callback
  name: string;
  icon: string;
  x: number;
  y: number;
  status: 'valid' | 'warning' | 'error' | 'placeholder' | 'unconfigured';
  errorCount?: number;
  actionType?: string;
  appService?: string;
}

interface UseDebouncedNodeConfigOptions {
  node: ConfigNode;
  onUpdate: (node: ConfigNode) => void;
  onClose: () => void;
}

interface UseDebouncedNodeConfigReturn {
  // State
  actionConfig: Record<string, unknown>;
  actionLabel: string;
  triggerFilters: FilterGroup | null;

  // Handlers (already debounced/safe)
  handleFieldChange: FieldChangeHandler;
  handleLabelChange: (newLabel: string) => void;
  handleTriggerFiltersChange: (filters: FilterGroup | null) => void;
  handleClose: () => void;
  applyDefaults: (defaults: Record<string, unknown>) => void;
}

/**
 * Manages debounced config updates for the ConfigPanel.
 *
 * Handles complex timing issues:
 * - Debounces field changes for live updates
 * - Flushes pending updates on node switch (saves previous node's changes)
 * - Flushes pending updates on unmount
 * - Ignores stale onChange calls from TipTap/VariableInput during field mount
 *
 * The ignore mechanism is critical: TipTap editors fire onChange during their
 * mount/unmount lifecycle with potentially stale values. We use a 100ms window
 * after node switch to ignore these calls.
 */
export function useDebouncedNodeConfig({
  node,
  onUpdate,
  onClose,
}: UseDebouncedNodeConfigOptions): UseDebouncedNodeConfigReturn {
  const [actionConfig, setActionConfig] = useState<Record<string, unknown>>(
    node.config ? { ...node.config } : {},
  );
  const [actionLabel, setActionLabel] = useState<string>(node.label || '');
  const [triggerFilters, setTriggerFilters] = useState<FilterGroup | null>(null);

  // Track the previous node ID to detect node switches
  const prevNodeIdRef = useRef<string | null>(null);
  // Flag to ignore field onChange during initialization (prevents stale data from field mount)
  const ignoreFieldChangesRef = useRef<boolean>(false);

  // Debounced update to parent via reusable hook
  const debouncedUpdate = useDebouncedCallback((updatedNode: ConfigNode) => {
    onUpdate(updatedNode);
  }, DEBOUNCE_DELAY);

  // Detect node change during render phase to set ignore flag BEFORE commit.
  // This prevents TipTap/VariableInput fields from corrupting data when they
  // fire onChange during unmount in the same commit phase.
  if (prevNodeIdRef.current !== null && prevNodeIdRef.current !== node.id) {
    ignoreFieldChangesRef.current = true;
  }

  // Sync actionLabel state when selected node changes
  useEffect(() => {
    setActionLabel(node.label || '');
  }, [node.id, node.label]);

  // Load trigger filters when node changes
  useEffect(() => {
    if (node.type === 'trigger' && node.config?.filters) {
      setTriggerFilters(node.config.filters as FilterGroup);
    } else {
      setTriggerFilters(null);
    }
  }, [node.id, node.type, node.config?.filters]);

  // Load existing node config into actionConfig state when selecting a different node
  useEffect(() => {
    // Flush any pending update for the previous node (saves user's changes),
    // then cancel the timer so it doesn't fire again.
    if (prevNodeIdRef.current !== null) {
      debouncedUpdate.flush();
    }

    // Update the previous node ID for next switch
    prevNodeIdRef.current = node.id;

    // Temporarily ignore field onChange calls during initialization.
    // Fields (especially TipTap/VariableInput) fire onChange on mount with initial/empty values,
    // which would create stale pending updates. We use a timeout to re-enable after all
    // field initialization is complete (microtask is too early, fields may fire in later renders).
    ignoreFieldChangesRef.current = true;
    setTimeout(() => {
      ignoreFieldChangesRef.current = false;
    }, 100);

    setActionConfig(node.config ? { ...node.config } : {});
    // Only re-run when node.id changes (selecting a different node), not when config updates
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [node.id]);

  // Flush pending updates on unmount (instead of discarding them)
  useEffect(() => {
    return () => {
      debouncedUpdate.flush();
    };
  }, [debouncedUpdate]);

  // Handle action field changes with debounced live update
  const handleFieldChange = useCallback(
    (fieldName: string, value: unknown) => {
      // Ignore onChange calls during field initialization (after node switch)
      if (ignoreFieldChangesRef.current) {
        return;
      }

      setActionConfig((prev) => {
        const newConfig = { ...prev, [fieldName]: value };

        // Auto-populate page_id when page_select is changed
        if (fieldName === 'page_select' && value) {
          newConfig['page_id'] = value;
        }

        // Debounced update to parent
        debouncedUpdate({
          ...node,
          config: newConfig,
          label: actionLabel || undefined,
        });

        return newConfig;
      });
    },
    [node, actionLabel, debouncedUpdate],
  );

  // Handle action label changes with debounced live update
  const handleLabelChange = useCallback(
    (newLabel: string) => {
      setActionLabel(newLabel);
      debouncedUpdate({
        ...node,
        config: actionConfig,
        label: newLabel || undefined,
      });
    },
    [node, actionConfig, debouncedUpdate],
  );

  // Handle trigger filter changes with immediate update
  const handleTriggerFiltersChange = useCallback(
    (filters: FilterGroup | null) => {
      setTriggerFilters(filters);
      // Immediately update the node with new filters
      onUpdate({
        ...node,
        config: {
          ...node.config,
          filters,
        },
      });
    },
    [node, onUpdate],
  );

  // Apply schema defaults to empty config (bypasses ignore window)
  const applyDefaults = useCallback(
    (defaults: Record<string, unknown>) => {
      setActionConfig((prev) => {
        // Only apply if config is effectively empty (new action)
        const hasUserValues = Object.values(prev).some(
          (v) => v !== undefined && v !== '' && v !== null,
        );
        if (hasUserValues) return prev;

        const newConfig = { ...defaults, ...prev };

        debouncedUpdate({
          ...node,
          config: newConfig,
          label: actionLabel || undefined,
        });

        return newConfig;
      });
    },
    [node, actionLabel, debouncedUpdate],
  );

  // Handle close with flush of pending updates
  const handleClose = useCallback(() => {
    // Flush any pending debounced update before closing
    debouncedUpdate.flush();
    onClose();
  }, [debouncedUpdate, onClose]);

  return {
    actionConfig,
    actionLabel,
    triggerFilters,
    handleFieldChange,
    handleLabelChange,
    handleTriggerFiltersChange,
    handleClose,
    applyDefaults,
  };
}
