/**
 * SpecRenderer - ReactFlow canvas that renders from AtomizerSpec v2.0
 *
 * This component replaces the legacy canvas approach with a spec-driven architecture:
 * - Reads from useSpecStore instead of useCanvasStore
 * - Converts spec to ReactFlow nodes/edges using spec converters
 * - All changes flow through the spec store and sync with backend
 * - Supports WebSocket real-time updates
 *
 * P2.7-P2.10: SpecRenderer component with node/edge/selection handling
 */

import { useCallback, useRef, useEffect, useMemo, useState, DragEvent } from 'react';
import { Play, Square, Loader2, Eye, EyeOff, CheckCircle, AlertCircle } from 'lucide-react';
import ReactFlow, {
  Background,
  Controls,
  MiniMap,
  ReactFlowProvider,
  ReactFlowInstance,
  Edge,
  Node,
  NodeChange,
  EdgeChange,
  Connection,
  applyNodeChanges,
} from 'reactflow';
import 'reactflow/dist/style.css';

import { nodeTypes } from './nodes';
import { specToNodes, specToEdges } from '../../lib/spec';
import {
  useSpecStore,
  useSpec,
  useSpecLoading,
  useSpecError,
  useSelectedNodeId,
  useSelectedEdgeId,
} from '../../hooks/useSpecStore';
import { useSpecWebSocket } from '../../hooks/useSpecWebSocket';
import { usePanelStore } from '../../hooks/usePanelStore';
import { useOptimizationStream } from '../../hooks/useOptimizationStream';
import { ConnectionStatusIndicator } from './ConnectionStatusIndicator';
import { ProgressRing } from './visualization/ConvergenceSparkline';
import { CanvasNodeData } from '../../lib/canvas/schema';
import { validateSpec, canRunOptimization } from '../../lib/validation/specValidator';

// ============================================================================
// Drag-Drop Helpers
// ============================================================================

import { SINGLETON_TYPES } from './palette/NodePalette';

/** All node types that can be added via drag-drop */
const ADDABLE_NODE_TYPES = ['model', 'solver', 'designVar', 'extractor', 'objective', 'constraint', 'algorithm', 'surrogate'] as const;
type AddableNodeType = typeof ADDABLE_NODE_TYPES[number];

function isAddableNodeType(type: string): type is AddableNodeType {
  return ADDABLE_NODE_TYPES.includes(type as AddableNodeType);
}

/** Check if a node type is a singleton (only one allowed) */
function isSingletonType(type: string): boolean {
  return SINGLETON_TYPES.includes(type as typeof SINGLETON_TYPES[number]);
}

/** Maps canvas NodeType to spec API type */
function mapNodeTypeToSpecType(type: AddableNodeType): 'designVar' | 'extractor' | 'objective' | 'constraint' | 'model' | 'solver' | 'algorithm' | 'surrogate' {
  return type;
}

/** Creates default data for a new node of the given type */
function getDefaultNodeData(type: AddableNodeType, position: { x: number; y: number }): Record<string, unknown> {
  const timestamp = Date.now();
  
  switch (type) {
    case 'model':
      return {
        name: 'Model',
        sim: {
          path: '',
          solver: 'nastran',
        },
        canvas_position: position,
      };
    case 'solver':
      return {
        name: 'Solver',
        engine: 'nxnastran',
        solution_type: 'SOL101',
        canvas_position: position,
      };
    case 'designVar':
      return {
        name: `variable_${timestamp}`,
        expression_name: `expr_${timestamp}`,
        type: 'continuous',
        bounds: { min: 0, max: 1 },
        baseline: 0.5,
        enabled: true,
        canvas_position: position,
      };
    case 'extractor':
      return {
        name: `extractor_${timestamp}`,
        type: 'custom_function',  // Must be valid ExtractorType
        builtin: false,
        enabled: true,
        // Custom function extractors need a function definition
        function: {
          name: 'extract',
          source_code: `def extract(op2_path: str, config: dict = None) -> dict:
    """
    Custom extractor function.
    
    Args:
        op2_path: Path to the OP2 results file
        config: Optional configuration dict
    
    Returns:
        Dictionary with extracted values
    """
    # TODO: Implement extraction logic
    return {'value': 0.0}
`,
        },
        outputs: [{ name: 'value', metric: 'custom' }],
        canvas_position: position,
      };
    case 'objective':
      return {
        name: `objective_${timestamp}`,
        direction: 'minimize',
        weight: 1.0,
        // Source is required - use placeholder that user must configure
        source: {
          extractor_id: 'ext_001',  // Placeholder - user needs to configure
          output_name: 'value',
        },
        canvas_position: position,
      };
    case 'constraint':
      return {
        name: `constraint_${timestamp}`,
        type: 'hard',  // Must be 'hard' or 'soft' (field is 'type' not 'constraint_type')
        operator: '<=',
        threshold: 1.0,  // Field is 'threshold' not 'limit'
        // Source is required
        source: {
          extractor_id: 'ext_001',  // Placeholder - user needs to configure
          output_name: 'value',
        },
        enabled: true,
        canvas_position: position,
      };
    case 'algorithm':
      return {
        name: 'Algorithm',
        type: 'TPE',
        budget: {
          max_trials: 100,
        },
        canvas_position: position,
      };
    case 'surrogate':
      return {
        name: 'Surrogate',
        enabled: false,
        model_type: 'MLP',
        min_trials: 20,
        canvas_position: position,
      };
  }
}

// ============================================================================
// Component Props
// ============================================================================

interface SpecRendererProps {
  /**
   * Optional study ID to load on mount.
   * If not provided, assumes spec is already loaded in the store.
   */
  studyId?: string;

  /**
   * Callback when study changes (for URL updates)
   */
  onStudyChange?: (studyId: string) => void;

  /**
   * Show loading overlay while spec is loading
   */
  showLoadingOverlay?: boolean;

  /**
   * Enable/disable editing (drag, connect, delete)
   */
  editable?: boolean;

  /**
   * Enable real-time WebSocket sync (default: true)
   */
  enableWebSocket?: boolean;

  /**
   * Show connection status indicator (default: true when WebSocket enabled)
   */
  showConnectionStatus?: boolean;
}

function SpecRendererInner({
  studyId,
  onStudyChange,
  showLoadingOverlay = true,
  editable = true,
  enableWebSocket = true,
  showConnectionStatus = true,
}: SpecRendererProps) {
  const reactFlowWrapper = useRef<HTMLDivElement>(null);
  const reactFlowInstance = useRef<ReactFlowInstance | null>(null);

  // Spec store state and actions
  const spec = useSpec();
  const isLoading = useSpecLoading();
  const error = useSpecError();
  const selectedNodeId = useSelectedNodeId();
  const selectedEdgeId = useSelectedEdgeId();

  const {
    loadSpec,
    selectNode,
    selectEdge,
    clearSelection,
    updateNodePosition,
    addNode,
    addEdge,
    removeEdge,
    removeNode,
    setError,
  } = useSpecStore();

  // WebSocket for real-time sync
  const storeStudyId = useSpecStore((s) => s.studyId);
  const wsStudyId = enableWebSocket ? storeStudyId : null;
  const { status: wsStatus } = useSpecWebSocket(wsStudyId);

  // Panel store for validation and error panels
  const { setValidationData, addError, openPanel } = usePanelStore();

  // Optimization WebSocket stream for real-time updates
  const {
    status: optimizationStatus,
    progress: wsProgress,
    bestTrial: wsBestTrial,
    recentTrials,
  } = useOptimizationStream(studyId, {
    autoReportErrors: true,
    onTrialComplete: (trial) => {
      console.log('[SpecRenderer] Trial completed:', trial.trial_number);
    },
    onNewBest: (best) => {
      console.log('[SpecRenderer] New best found:', best.value);
      setShowResults(true); // Auto-show results when new best found
    },
  });

  // Optimization execution state
  const isRunning = optimizationStatus === 'running';
  const [isStarting, setIsStarting] = useState(false);
  const [showResults, setShowResults] = useState(false);
  const [validationStatus, setValidationStatus] = useState<'valid' | 'invalid' | 'unchecked'>('unchecked');

  // Build trial history for sparklines (extract objective values from recent trials)
  const trialHistory = useMemo(() => {
    const history: Record<string, number[]> = {};
    for (const trial of recentTrials) {
      // Map objective values - assumes single objective for now
      if (trial.objective !== null) {
        const key = 'primary';
        if (!history[key]) history[key] = [];
        history[key].push(trial.objective);
      }
      // Could also extract individual params/results for multi-objective
    }
    // Reverse so oldest is first (for sparkline)
    for (const key of Object.keys(history)) {
      history[key].reverse();
    }
    return history;
  }, [recentTrials]);

  // Build best trial data for node display
  const bestTrial = useMemo((): {
    trial_number: number;
    objective: number;
    design_variables: Record<string, number>;
    results: Record<string, number>;
  } | null => {
    if (!wsBestTrial) return null;
    return {
      trial_number: wsBestTrial.trial_number,
      objective: wsBestTrial.value,
      design_variables: wsBestTrial.params,
      results: { primary: wsBestTrial.value, ...wsBestTrial.params },
    };
  }, [wsBestTrial]);

  // Note: Polling removed - now using WebSocket via useOptimizationStream hook
  // The hook handles: status updates, best trial updates, error reporting

  // Validate the spec and show results in panel
  const handleValidate = useCallback(() => {
    if (!spec) return;
    
    const result = validateSpec(spec);
    setValidationData(result);
    setValidationStatus(result.valid ? 'valid' : 'invalid');
    
    // Auto-open validation panel if there are issues
    if (!result.valid || result.warnings.length > 0) {
      openPanel('validation');
    }
    
    return result;
  }, [spec, setValidationData, openPanel]);

  const handleRun = async () => {
    if (!studyId || !spec) return;
    
    // Validate before running
    const validation = handleValidate();
    if (!validation || !validation.valid) {
      // Show validation panel with errors
      return;
    }
    
    // Also do a quick sanity check
    const { canRun, reason } = canRunOptimization(spec);
    if (!canRun) {
      addError({
        type: 'config_error',
        message: reason || 'Cannot run optimization',
        recoverable: false,
        suggestions: ['Check the validation panel for details'],
        timestamp: Date.now(),
      });
      return;
    }
    
    setIsStarting(true);
    try {
      const res = await fetch(`/api/optimization/studies/${studyId}/run`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ trials: spec?.optimization?.budget?.max_trials || 50 })
      });
      if (!res.ok) {
        const err = await res.json();
        throw new Error(err.detail || 'Failed to start');
      }
      // isRunning is now derived from WebSocket state (optimizationStatus === 'running')
      setValidationStatus('unchecked'); // Clear validation status when running
    } catch (e) {
      const errorMessage = e instanceof Error ? e.message : 'Failed to start optimization';
      setError(errorMessage);
      
      // Also add to error panel for persistence
      addError({
        type: 'system_error',
        message: errorMessage,
        recoverable: true,
        suggestions: ['Check if the backend is running', 'Verify the study configuration'],
        timestamp: Date.now(),
      });
    } finally {
      setIsStarting(false);
    }
  };

  const handleStop = async () => {
    if (!studyId) return;
    try {
      const res = await fetch(`/api/optimization/studies/${studyId}/stop`, { method: 'POST' });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || 'Failed to stop');
      }
      // isRunning will update via WebSocket when optimization actually stops
    } catch (e) {
      const errorMessage = e instanceof Error ? e.message : 'Failed to stop optimization';
      setError(errorMessage);
      addError({
        type: 'system_error',
        message: errorMessage,
        recoverable: false,
        suggestions: ['The optimization may still be running in the background'],
        timestamp: Date.now(),
      });
    }
  };

  // Load spec on mount if studyId provided
  useEffect(() => {
    if (studyId) {
      loadSpec(studyId).then(() => {
        if (onStudyChange) {
          onStudyChange(studyId);
        }
      });
    }
  }, [studyId, loadSpec, onStudyChange]);

  // Convert spec to ReactFlow nodes
  const nodes = useMemo(() => {
    const baseNodes = specToNodes(spec);
    
    // Always map nodes to include history for sparklines (even if not showing results)
    return baseNodes.map(node => {
      // Create a mutable copy with explicit any type for dynamic property assignment
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const newData: any = { ...node.data };
      
      // Add history for sparklines on objective nodes
      if (node.type === 'objective') {
        newData.history = trialHistory['primary'] || [];
      }
      
      // Map results to nodes when showing results
      if (showResults && bestTrial) {
        if (node.type === 'designVar' && newData.expressionName) {
          const val = bestTrial.design_variables?.[newData.expressionName];
          if (val !== undefined) newData.resultValue = val;
        } else if (node.type === 'objective') {
          const outputName = newData.outputName;
          if (outputName && bestTrial.results?.[outputName] !== undefined) {
            newData.resultValue = bestTrial.results[outputName];
          }
        } else if (node.type === 'constraint') {
          const outputName = newData.outputName;
          if (outputName && bestTrial.results?.[outputName] !== undefined) {
            const val = bestTrial.results[outputName];
            newData.resultValue = val;
            
            // Check feasibility
            const op = newData.operator;
            const threshold = newData.value;
            if (op === '<=' && threshold !== undefined) newData.isFeasible = val <= threshold;
            else if (op === '>=' && threshold !== undefined) newData.isFeasible = val >= threshold;
            else if (op === '<' && threshold !== undefined) newData.isFeasible = val < threshold;
            else if (op === '>' && threshold !== undefined) newData.isFeasible = val > threshold;
            else if (op === '==' && threshold !== undefined) newData.isFeasible = Math.abs(val - threshold) < 1e-6;
          }
        } else if (node.type === 'extractor') {
          const outputNames = newData.outputNames;
          if (outputNames && outputNames.length > 0 && bestTrial.results) {
            const firstOut = outputNames[0];
            if (bestTrial.results[firstOut] !== undefined) {
              newData.resultValue = bestTrial.results[firstOut];
            }
          }
        }
      }
      
      return { ...node, data: newData };
    });
  }, [spec, showResults, bestTrial, trialHistory]);

  // Convert spec to ReactFlow edges with selection styling
  const edges = useMemo(() => {
    const baseEdges = specToEdges(spec);
    return baseEdges.map((edge) => ({
      ...edge,
      style: {
        stroke: edge.id === selectedEdgeId ? '#60a5fa' : '#6b7280',
        strokeWidth: edge.id === selectedEdgeId ? 3 : 2,
      },
      animated: edge.id === selectedEdgeId,
    }));
  }, [spec, selectedEdgeId]);

  // Track node positions for change handling
  const nodesRef = useRef<Node<CanvasNodeData>[]>(nodes);
  useEffect(() => {
    nodesRef.current = nodes;
  }, [nodes]);

  // Track local node state for smooth dragging
  const [localNodes, setLocalNodes] = useState(nodes);
  
  // Sync local nodes with spec-derived nodes when spec changes
  useEffect(() => {
    setLocalNodes(nodes);
  }, [nodes]);

  // Handle node position changes
  const onNodesChange = useCallback(
    (changes: NodeChange[]) => {
      if (!editable) return;

      // Apply changes to local state for smooth dragging
      setLocalNodes((nds) => applyNodeChanges(changes, nds));

      // Handle position changes - save to spec when drag ends
      for (const change of changes) {
        if (change.type === 'position' && change.position && change.dragging === false) {
          // Dragging ended - update spec
          updateNodePosition(change.id, {
            x: change.position.x,
            y: change.position.y,
          });
        }
      }
    },
    [editable, updateNodePosition]
  );

  // Handle edge changes (deletion)
  const onEdgesChange = useCallback(
    (changes: EdgeChange[]) => {
      if (!editable) return;

      for (const change of changes) {
        if (change.type === 'remove') {
          // Find the edge being removed
          const edge = edges.find((e) => e.id === change.id);
          if (edge) {
            removeEdge(edge.source, edge.target).catch((err) => {
              console.error('Failed to remove edge:', err);
              setError(err.message);
            });
          }
        }
      }
    },
    [editable, edges, removeEdge, setError]
  );

  // Handle new connections
  const onConnect = useCallback(
    (connection: Connection) => {
      if (!editable) return;
      if (!connection.source || !connection.target) return;

      addEdge(connection.source, connection.target).catch((err) => {
        console.error('Failed to add edge:', err);
        setError(err.message);
      });
    },
    [editable, addEdge, setError]
  );

  // Handle node clicks for selection
  const onNodeClick = useCallback(
    (_: React.MouseEvent, node: { id: string }) => {
      selectNode(node.id);
    },
    [selectNode]
  );

  // Handle edge clicks for selection
  const onEdgeClick = useCallback(
    (_: React.MouseEvent, edge: Edge) => {
      selectEdge(edge.id);
    },
    [selectEdge]
  );

  // Handle pane clicks to clear selection
  const onPaneClick = useCallback(() => {
    clearSelection();
  }, [clearSelection]);

  // Keyboard handler for Delete/Backspace
  useEffect(() => {
    if (!editable) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Delete' || event.key === 'Backspace') {
        const target = event.target as HTMLElement;
        if (
          target.tagName === 'INPUT' ||
          target.tagName === 'TEXTAREA' ||
          target.isContentEditable
        ) {
          return;
        }

        // Delete selected edge first
        if (selectedEdgeId) {
          const edge = edges.find((e) => e.id === selectedEdgeId);
          if (edge) {
            removeEdge(edge.source, edge.target).catch((err) => {
              console.error('Failed to delete edge:', err);
              setError(err.message);
            });
          }
          return;
        }

        // Delete selected node
        if (selectedNodeId) {
          // Don't allow deleting synthetic nodes (model, solver, optimization)
          if (['model', 'solver', 'optimization', 'surrogate'].includes(selectedNodeId)) {
            return;
          }

          removeNode(selectedNodeId).catch((err) => {
            console.error('Failed to delete node:', err);
            setError(err.message);
          });
        }
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [editable, selectedNodeId, selectedEdgeId, edges, removeNode, removeEdge, setError]);

  // =========================================================================
  // Drag-Drop Handlers
  // =========================================================================

  const onDragOver = useCallback(
    (event: DragEvent) => {
      if (!editable) return;
      event.preventDefault();
      event.dataTransfer.dropEffect = 'move';
    },
    [editable]
  );

  const onDrop = useCallback(
    async (event: DragEvent) => {
      if (!editable || !reactFlowInstance.current) return;
      event.preventDefault();

      const type = event.dataTransfer.getData('application/reactflow');
      if (!type || !isAddableNodeType(type)) {
        console.warn('Invalid or non-addable node type dropped:', type);
        return;
      }

      // Check if this is a singleton type that already exists
      if (isSingletonType(type)) {
        const existingNode = localNodes.find(n => n.type === type);
        if (existingNode) {
          // Select the existing node instead of creating a duplicate
          selectNode(existingNode.id);
          // Show a toast notification would be nice here
          console.log(`${type} already exists - selected existing node`);
          return;
        }
      }

      // Convert screen position to flow position
      const position = reactFlowInstance.current.screenToFlowPosition({
        x: event.clientX,
        y: event.clientY,
      });

      // Create default data for the node
      const nodeData = getDefaultNodeData(type, position);
      const specType = mapNodeTypeToSpecType(type);

      // For structural types (model, solver, algorithm, surrogate), these are 
      // part of the spec structure rather than array items. Handle differently.
      const structuralTypes = ['model', 'solver', 'algorithm', 'surrogate'];
      if (structuralTypes.includes(type)) {
        // These nodes are derived from spec structure - they shouldn't be "added"
        // They already exist if the spec has that section configured
        console.log(`${type} is a structural node - configure via spec directly`);
        setError(`${type} nodes are configured via the spec. Use the config panel to edit.`);
        return;
      }

      try {
        const nodeId = await addNode(specType as 'designVar' | 'extractor' | 'objective' | 'constraint', nodeData);
        // Select the newly created node
        selectNode(nodeId);
      } catch (err) {
        console.error('Failed to add node:', err);
        setError(err instanceof Error ? err.message : 'Failed to add node');
      }
    },
    [editable, addNode, selectNode, setError, localNodes]
  );

  // Loading state
  if (showLoadingOverlay && isLoading && !spec) {
    return (
      <div className="flex-1 flex items-center justify-center bg-dark-900">
        <div className="text-center">
          <div className="animate-spin w-8 h-8 border-2 border-primary-500 border-t-transparent rounded-full mx-auto mb-4" />
          <p className="text-dark-400">Loading spec...</p>
        </div>
      </div>
    );
  }

  // Error state
  if (error && !spec) {
    return (
      <div className="flex-1 flex items-center justify-center bg-dark-900">
        <div className="text-center max-w-md">
          <div className="w-12 h-12 rounded-full bg-red-900/50 flex items-center justify-center mx-auto mb-4">
            <svg className="w-6 h-6 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
          </div>
          <h3 className="text-lg font-medium text-white mb-2">Failed to load spec</h3>
          <p className="text-dark-400 mb-4">{error}</p>
          {studyId && (
            <button
              onClick={() => loadSpec(studyId)}
              className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-500 transition-colors"
            >
              Retry
            </button>
          )}
        </div>
      </div>
    );
  }

  // Empty state
  if (!spec) {
    return (
      <div className="flex-1 flex items-center justify-center bg-dark-900">
        <div className="text-center">
          <p className="text-dark-400">No spec loaded</p>
          <p className="text-dark-500 text-sm mt-2">Load a study to see its optimization configuration</p>
        </div>
      </div>
    );
  }

  return (
    <div className="w-full h-full relative" ref={reactFlowWrapper}>
      {/* Status indicators (overlay) */}
      <div className="absolute top-4 right-4 z-20 flex items-center gap-2">
        {/* WebSocket connection status */}
        {enableWebSocket && showConnectionStatus && (
          <div className="px-3 py-1.5 bg-dark-800/90 backdrop-blur rounded-lg border border-dark-600">
            <ConnectionStatusIndicator status={wsStatus} />
          </div>
        )}

        {/* Loading indicator */}
        {isLoading && (
          <div className="flex items-center gap-2 px-3 py-1.5 bg-dark-800/90 backdrop-blur rounded-lg border border-dark-600">
            <div className="animate-spin w-4 h-4 border-2 border-primary-500 border-t-transparent rounded-full" />
            <span className="text-xs text-dark-300">Syncing...</span>
          </div>
        )}
      </div>

      {/* Error banner (overlay) */}
      {error && (
        <div className="absolute top-4 left-4 right-20 z-20 px-4 py-2 bg-red-900/90 backdrop-blur border border-red-700 text-red-200 rounded-lg text-sm flex justify-between items-center">
          <span>{error}</span>
          <button
            onClick={() => setError(null)}
            className="text-red-400 hover:text-red-200 ml-2"
          >
            ×
          </button>
        </div>
      )}

      <ReactFlow
        nodes={localNodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onInit={(instance) => {
          reactFlowInstance.current = instance;
          // Auto-fit view on init with padding
          setTimeout(() => instance.fitView({ padding: 0.2, duration: 300 }), 100);
        }}
        onDragOver={onDragOver}
        onDrop={onDrop}
        onNodeClick={onNodeClick}
        onEdgeClick={onEdgeClick}
        onPaneClick={onPaneClick}
        nodeTypes={nodeTypes}
        fitView
        fitViewOptions={{ padding: 0.2, includeHiddenNodes: false }}
        deleteKeyCode={null} // We handle delete ourselves
        nodesDraggable={editable}
        nodesConnectable={editable}
        elementsSelectable={true}
        className="bg-dark-900"
      >
        <Background color="#374151" gap={20} />
        <Controls className="!bg-dark-800 !border-dark-600 !rounded-lg [&>button]:!bg-dark-700 [&>button]:!border-dark-600 [&>button]:!fill-dark-300 [&>button:hover]:!bg-dark-600" />
        <MiniMap
          className="!bg-dark-800 !border-dark-600 !rounded-lg"
          nodeColor="#4B5563"
          maskColor="rgba(0, 0, 0, 0.5)"
        />
      </ReactFlow>

      {/* Action Buttons */}
      <div className="absolute bottom-4 right-4 z-10 flex gap-2">
        {/* Results toggle */}
        {bestTrial && (
          <button
            onClick={() => setShowResults(!showResults)}
            className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors border ${
              showResults
                ? 'bg-primary-600/90 text-white border-primary-500 hover:bg-primary-500'
                : 'bg-dark-800 text-dark-300 border-dark-600 hover:text-white hover:border-dark-500'
            }`}
            title={showResults ? "Hide Results" : "Show Best Trial Results"}
          >
            {showResults ? <Eye size={16} /> : <EyeOff size={16} />}
            <span className="text-sm font-medium">Results</span>
          </button>
        )}

        {/* Validate button - shows validation status */}
        <button
          onClick={handleValidate}
          className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors border ${
            validationStatus === 'valid'
              ? 'bg-green-600/20 text-green-400 border-green-500/50 hover:bg-green-600/30'
              : validationStatus === 'invalid'
              ? 'bg-red-600/20 text-red-400 border-red-500/50 hover:bg-red-600/30'
              : 'bg-dark-800 text-dark-300 border-dark-600 hover:text-white hover:border-dark-500'
          }`}
          title="Validate spec before running"
        >
          {validationStatus === 'valid' ? (
            <CheckCircle size={16} />
          ) : validationStatus === 'invalid' ? (
            <AlertCircle size={16} />
          ) : (
            <CheckCircle size={16} />
          )}
          <span className="text-sm font-medium">Validate</span>
        </button>

        {/* Run/Stop button */}
        {isRunning ? (
          <button
            onClick={handleStop}
            className="flex items-center gap-2 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-500 shadow-lg transition-colors font-medium"
          >
            <Square size={16} fill="currentColor" />
            Stop
          </button>
        ) : (
          <button
            onClick={handleRun}
            disabled={isStarting || validationStatus === 'invalid'}
            className={`flex items-center gap-2 px-4 py-2 rounded-lg shadow-lg transition-colors font-medium ${
              validationStatus === 'invalid'
                ? 'bg-dark-700 text-dark-400 cursor-not-allowed'
                : 'bg-emerald-600 text-white hover:bg-emerald-500 disabled:opacity-50 disabled:cursor-not-allowed'
            }`}
            title={validationStatus === 'invalid' ? 'Fix validation errors first' : 'Start optimization'}
          >
            {isStarting ? (
              <Loader2 size={16} className="animate-spin" />
            ) : (
              <Play size={16} fill="currentColor" />
            )}
            Run
          </button>
        )}
      </div>

      {/* Study name badge */}
      <div className="absolute bottom-4 left-4 z-10 px-3 py-1.5 bg-dark-800/90 backdrop-blur rounded-lg border border-dark-600">
        <span className="text-sm text-dark-300">{spec.meta.study_name}</span>
      </div>

      {/* Progress indicator when running */}
      {isRunning && wsProgress && (
        <div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-3 px-4 py-2 bg-dark-800/95 backdrop-blur rounded-lg border border-dark-600 shadow-lg">
          <ProgressRing 
            progress={wsProgress.percentage} 
            size={36} 
            strokeWidth={3}
            color="#10b981"
          />
          <div className="flex flex-col">
            <span className="text-sm font-medium text-white">
              Trial {wsProgress.current} / {wsProgress.total}
            </span>
            <span className="text-xs text-dark-400">
              {wsProgress.fea_count > 0 && `${wsProgress.fea_count} FEA`}
              {wsProgress.fea_count > 0 && wsProgress.nn_count > 0 && ' + '}
              {wsProgress.nn_count > 0 && `${wsProgress.nn_count} NN`}
              {wsProgress.fea_count === 0 && wsProgress.nn_count === 0 && 'Running...'}
            </span>
          </div>
          {wsBestTrial && (
            <div className="flex flex-col border-l border-dark-600 pl-3 ml-1">
              <span className="text-xs text-dark-400">Best</span>
              <span className="text-sm font-medium text-emerald-400">
                {typeof wsBestTrial.value === 'number' 
                  ? wsBestTrial.value.toFixed(4) 
                  : wsBestTrial.value}
              </span>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/**
 * SpecRenderer with ReactFlowProvider wrapper.
 *
 * Usage:
 * ```tsx
 * // Load spec on mount
 * <SpecRenderer studyId="M1_Mirror/m1_mirror_flatback" />
 *
 * // Use with already-loaded spec
 * const { loadSpec } = useSpecStore();
 * await loadSpec('M1_Mirror/m1_mirror_flatback');
 * <SpecRenderer />
 * ```
 */
export function SpecRenderer(props: SpecRendererProps) {
  return (
    <ReactFlowProvider>
      <SpecRendererInner {...props} />
    </ReactFlowProvider>
  );
}

export default SpecRenderer;
