import { useCallback, useRef, useState } from 'react';
import type { NodeAction, NodeConfig } from '../ProcessDiagram/types';
import type { ExecutionDetail, ProcessorNode } from './types';
import { useExecutionDetail } from '../../api/queries/executions';
import { useDiagramLayout } from '../../api/queries/diagrams';
import { ProcessDiagram } from '../ProcessDiagram';
import { DetailPanel } from './DetailPanel';
import { useExecutionOverlay } from './useExecutionOverlay';
import { useIterationState } from './useIterationState';
import styles from './ExecutionDiagram.module.css';

interface ExecutionDiagramProps {
  executionId: string;
  executionDetail?: ExecutionDetail;
  direction?: 'LR' | 'TB';
  knownRouteIds?: Set<string>;
  endpointRouteMap?: Map<string, string>;
  onNodeAction?: (nodeId: string, action: NodeAction) => void;
  nodeConfigs?: Map<string, NodeConfig>;
  className?: string;
}

/**
 * Find a processor in the tree, respecting iteration filtering.
 * Only recurses into children matching the selected iteration index
 * so the returned ProcessorNode has data from the correct iteration.
 */
function findProcessorInTree(
  nodes: ProcessorNode[] | undefined,
  processorId: string | null,
  iterationState?: Map<string, import('./types').IterationInfo>,
  parentId?: string,
): ProcessorNode | null {
  if (!nodes || !processorId) return null;
  for (const n of nodes) {
    if (!n.processorId) continue;

    // If parent is an iteration container, skip children from other iterations
    if (parentId && iterationState?.has(parentId)) {
      const info = iterationState.get(parentId)!;
      if (n.iteration != null && n.iteration !== info.current) continue;
    }

    if (n.processorId === processorId) return n;
    if (n.children) {
      const found = findProcessorInTree(n.children, processorId, iterationState, n.processorId);
      if (found) return found;
    }
  }
  return null;
}

function findFailedProcessor(nodes: ProcessorNode[]): ProcessorNode | null {
  for (const n of nodes) {
    if (n.status === 'FAILED') return n;
    if (n.children) {
      const found = findFailedProcessor(n.children);
      if (found) return found;
    }
  }
  return null;
}

function statusBadgeClass(status: string): string {
  const s = status?.toUpperCase();
  if (s === 'COMPLETED') return `${styles.statusBadge} ${styles.statusCompleted}`;
  if (s === 'FAILED') return `${styles.statusBadge} ${styles.statusFailed}`;
  if (s === 'RUNNING') return `${styles.statusBadge} ${styles.statusRunning}`;
  return styles.statusBadge;
}

export function ExecutionDiagram({
  executionId,
  executionDetail: externalDetail,
  direction = 'LR',
  knownRouteIds,
  endpointRouteMap,
  onNodeAction,
  nodeConfigs,
  className,
}: ExecutionDiagramProps) {
  // 1. Fetch execution data (skip if pre-fetched prop provided)
  const detailQuery = useExecutionDetail(externalDetail ? null : executionId);
  const detail = externalDetail ?? detailQuery.data;
  const detailLoading = !externalDetail && detailQuery.isLoading;
  const detailError = !externalDetail && detailQuery.error;

  // 2. Load diagram by content hash
  const diagramQuery = useDiagramLayout(detail?.diagramContentHash ?? null, direction);
  const diagramLayout = diagramQuery.data;
  const diagramLoading = diagramQuery.isLoading;
  const diagramError = diagramQuery.error;

  // 3. Initialize iteration state
  const { iterationState, setIteration } = useIterationState(detail?.processors);

  // 4. Compute overlay
  const overlay = useExecutionOverlay(detail?.processors, iterationState);

  // 5. Manage selection + center-on-node
  const [selectedProcessorId, setSelectedProcessorId] = useState<string>('');
  const [centerOnNodeId, setCenterOnNodeId] = useState<string>('');

  // 6. Resizable splitter state
  const [splitPercent, setSplitPercent] = useState(60);
  const containerRef = useRef<HTMLDivElement>(null);

  const handleSplitterDown = useCallback((e: React.PointerEvent) => {
    e.currentTarget.setPointerCapture(e.pointerId);
    const container = containerRef.current;
    if (!container) return;
    const onMove = (me: PointerEvent) => {
      const rect = container.getBoundingClientRect();
      const y = me.clientY - rect.top;
      const pct = Math.min(85, Math.max(30, (y / rect.height) * 100));
      setSplitPercent(pct);
    };
    const onUp = () => {
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
    };
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
  }, []);

  // Jump to error: find first FAILED processor, select it, and center the viewport
  const handleJumpToError = useCallback(() => {
    if (!detail?.processors) return;
    const failed = findFailedProcessor(detail.processors);
    if (failed?.processorId) {
      setSelectedProcessorId(failed.processorId);
      // Use a unique value to re-trigger centering even if the same node
      setCenterOnNodeId('');
      requestAnimationFrame(() => setCenterOnNodeId(failed.processorId));
    }
  }, [detail?.processors]);

  const handleDownloadJson = useCallback(() => {
    if (!detail) return;
    const json = JSON.stringify(detail, null, 2);
    const blob = new Blob([json], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `execution-${executionId}.json`;
    a.click();
    URL.revokeObjectURL(url);
  }, [detail, executionId]);

  // Loading state
  if (detailLoading || (detail && diagramLoading)) {
    return (
      <div className={`${styles.executionDiagram} ${className ?? ''}`}>
        <div className={styles.loadingState}>Loading execution data...</div>
      </div>
    );
  }

  // Error state
  if (detailError) {
    return (
      <div className={`${styles.executionDiagram} ${className ?? ''}`}>
        <div className={styles.errorState}>Failed to load execution detail</div>
      </div>
    );
  }

  if (diagramError) {
    return (
      <div className={`${styles.executionDiagram} ${className ?? ''}`}>
        <div className={styles.errorState}>Failed to load diagram</div>
      </div>
    );
  }

  if (!detail) {
    return (
      <div className={`${styles.executionDiagram} ${className ?? ''}`}>
        <div className={styles.loadingState}>No execution data</div>
      </div>
    );
  }

  return (
    <div ref={containerRef} className={`${styles.executionDiagram} ${className ?? ''}`}>
      {/* Diagram area */}
      <div className={styles.diagramArea} style={{ height: `${splitPercent}%` }}>
        <button
          className={styles.downloadBtn}
          onClick={handleDownloadJson}
          title="Download execution JSON"
        >
          ↓ JSON
        </button>
        <ProcessDiagram
          application={detail.applicationId}
          routeId={detail.routeId}
          direction={direction}
          diagramLayout={diagramLayout}
          selectedNodeId={selectedProcessorId}
          onNodeSelect={setSelectedProcessorId}
          onNodeAction={onNodeAction}
          nodeConfigs={nodeConfigs}
          knownRouteIds={knownRouteIds}
          endpointRouteMap={endpointRouteMap}
          executionOverlay={overlay}
          iterationState={iterationState}
          onIterationChange={setIteration}
          centerOnNodeId={centerOnNodeId}
        />
      </div>

      {/* Resizable splitter */}
      <div
        className={styles.splitter}
        onPointerDown={handleSplitterDown}
      />

      {/* Detail panel */}
      <div className={styles.detailArea} style={{ height: `${100 - splitPercent}%` }}>
        <DetailPanel
          selectedProcessor={
            selectedProcessorId && overlay.has(selectedProcessorId)
              ? findProcessorInTree(detail.processors, selectedProcessorId, iterationState)
              : null
          }
          executionDetail={detail}
          executionId={executionId}
          onSelectProcessor={setSelectedProcessorId}
        />
      </div>
    </div>
  );
}
