import type { DiagramNode as DiagramNodeType, DiagramEdge as DiagramEdgeType } from '../../api/queries/diagrams';
import type { NodeConfig, LatencyHeatmapEntry } from './types';
import type { NodeExecutionState, IterationInfo } from '../ExecutionDiagram/types';
import { colorForType, isCompoundType, iconForType, type IconElement } from './node-colors';
import { DiagramNode } from './DiagramNode';
import { DiagramEdge } from './DiagramEdge';
import styles from './ProcessDiagram.module.css';

const HEADER_HEIGHT = 22;
const CORNER_RADIUS = 4;

interface CompoundNodeProps {
  node: DiagramNodeType;
  /** All edges for this section — compound filters to its own internal edges */
  edges: DiagramEdgeType[];
  /** Absolute offset of the nearest compound ancestor (for coordinate adjustment) */
  parentX?: number;
  parentY?: number;
  selectedNodeId?: string;
  hoveredNodeId: string | null;
  nodeConfigs?: Map<string, NodeConfig>;
  /** Execution overlay for edge traversal coloring */
  executionOverlay?: Map<string, NodeExecutionState>;
  /** Whether an execution overlay is active (enables dimming of skipped nodes) */
  overlayActive?: boolean;
  /** Per-compound iteration state */
  iterationState?: Map<string, IterationInfo>;
  /** Called when user changes iteration on a compound stepper */
  onIterationChange?: (compoundNodeId: string, iterationIndex: number) => void;
  latencyHeatmap?: Map<string, LatencyHeatmapEntry>;
  onNodeClick: (nodeId: string) => void;
  onNodeDoubleClick?: (nodeId: string) => void;
  onNodeEnter: (nodeId: string) => void;
  onNodeLeave: () => void;
}

export function CompoundNode({
  node, edges, parentX = 0, parentY = 0,
  selectedNodeId, hoveredNodeId, nodeConfigs, executionOverlay,
  overlayActive, iterationState, onIterationChange, latencyHeatmap,
  onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave,
}: CompoundNodeProps) {
  const x = (node.x ?? 0) - parentX;
  const y = (node.y ?? 0) - parentY;
  const absX = node.x ?? 0;
  const absY = node.y ?? 0;
  const w = node.width ?? 200;
  const h = node.height ?? 100;
  const color = colorForType(node.type);
  const typeName = node.type?.replace(/^EIP_/, '').replace(/_/g, ' ') ?? '';
  const label = node.label ? `${typeName}: ${node.label}` : typeName;
  const iterationInfo = node.id ? iterationState?.get(node.id) : undefined;
  const headerWidth = w;

  // Collect all descendant node IDs to filter edges that belong inside this compound
  const descendantIds = new Set<string>();
  collectIds(node.children ?? [], descendantIds);

  const internalEdges = edges.filter(
    e => descendantIds.has(e.sourceId) && descendantIds.has(e.targetId),
  );

  const childProps = {
    edges, selectedNodeId, hoveredNodeId, nodeConfigs, executionOverlay,
    overlayActive, iterationState, onIterationChange, latencyHeatmap,
    onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave,
  };

  // Execution overlay state for this compound
  const ownState = node.id ? executionOverlay?.get(node.id) : undefined;
  const executedDescendant = hasExecutedDescendant(node, executionOverlay);
  const isCompleted = ownState?.status === 'COMPLETED';
  const isFailed = ownState?.status === 'FAILED';
  const descendantFailed = executedDescendant && hasFailedDescendant(node, executionOverlay);

  // Gated = gate processor blocked all children from executing
  const isGated = ownState && !executedDescendant
    && (ownState.filterMatched === false || ownState.duplicateMessage === true);

  // Color: own status first, then infer from descendants (for path containers like when/otherwise)
  const effectiveColor = isGated ? 'var(--amber)'
    : isFailed || descendantFailed ? 'var(--error)'
    : isCompleted || executedDescendant ? 'var(--success)'
    : color;

  // Dim compound when overlay is active but neither the compound nor any
  // descendant was executed in the current iteration.
  const isSkipped = overlayActive && !ownState && !hasExecutedDescendant(node, executionOverlay);

  // _TRY_BODY / _CB_MAIN: transparent wrapper — no header, no border, just layout
  if (node.type === '_TRY_BODY' || node.type === '_CB_MAIN') {
    return (
      <g transform={`translate(${x}, ${y})`}>
        {renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
        {renderChildren(node, absX, absY, childProps)}
      </g>
    );
  }

  // _CB_FALLBACK: section styling with EIP purple
  if (node.type === '_CB_FALLBACK') {
    const fallbackColor = 'var(--purple)';
    return (
      <g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
        <rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
          fill={fallbackColor} fillOpacity={0.06} />
        <rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
          fill="none" stroke={fallbackColor} strokeWidth={1} strokeOpacity={0.4} strokeDasharray="4 2" />
        <text x={8} y={12} fill={fallbackColor} fontSize={10} fontWeight={600}>
          fallback
        </text>
        {renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
        {renderChildren(node, absX, absY, childProps)}
      </g>
    );
  }

  // DO_CATCH / DO_FINALLY: section-like styling (tinted bg, thin border, label)
  if (node.type === 'DO_CATCH' || node.type === 'DO_FINALLY') {
    const sectionLabel = node.type === 'DO_CATCH'
      ? (node.label ? `catch: ${node.label}` : 'catch')
      : (node.label ? `finally: ${node.label}` : 'finally');

    return (
      <g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
        {/* Tinted background */}
        <rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
          fill={color} fillOpacity={0.06} />
        {/* Border */}
        <rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
          fill="none" stroke={color} strokeWidth={1} strokeOpacity={0.4} />
        {/* Section label */}
        <text x={8} y={12} fill={color} fontSize={10} fontWeight={600}>
          {sectionLabel}
        </text>
        {renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
        {renderChildren(node, absX, absY, childProps)}
      </g>
    );
  }

  // Default compound rendering (DO_TRY, EIP_CHOICE, EIP_FILTER, EIP_IDEMPOTENT_CONSUMER, etc.)
  const containerFill = isGated ? 'var(--amber-bg)' : 'var(--bg-surface)';
  return (
    <g data-node-id={node.id} transform={`translate(${x}, ${y})`} opacity={isSkipped ? 0.35 : undefined}>
      {/* Container body */}
      <rect
        x={0}
        y={0}
        width={w}
        height={h}
        rx={CORNER_RADIUS}
        fill={containerFill}
        stroke={effectiveColor}
        strokeWidth={isGated ? 2 : 1.5}
      />

      {/* Colored header bar */}
      <rect x={0} y={0} width={w} height={HEADER_HEIGHT} rx={CORNER_RADIUS} fill={effectiveColor} />
      <rect x={CORNER_RADIUS} y={CORNER_RADIUS} width={w - CORNER_RADIUS * 2} height={HEADER_HEIGHT - CORNER_RADIUS} fill={effectiveColor} />

      {/* Header icon (left-aligned) */}
      <g transform={`translate(6, ${HEADER_HEIGHT / 2 - 5}) scale(0.417)`}>
        {iconForType(node.type).map((el: IconElement, i: number) =>
          'd' in el
            ? <path key={i} d={el.d} fill="none" stroke="var(--text-inverse)" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
            : <circle key={i} cx={el.cx} cy={el.cy} r={el.r} fill="none" stroke="var(--text-inverse)" strokeWidth={2} />
        )}
      </g>
      {/* Header label (centered) */}
      <text
        x={w / 2}
        y={HEADER_HEIGHT / 2 + 4}
        fill="var(--text-inverse)"
        fontSize={10}
        fontWeight={600}
        textAnchor="middle"
      >
        {label}
      </text>

      {/* Iteration stepper (for LOOP, SPLIT, MULTICAST with overlay data) */}
      {iterationInfo && (
        <foreignObject x={headerWidth - 80} y={1} width={75} height={20}>
          <div className={styles.iterationStepper}>
            <button
              onClick={(e) => { e.stopPropagation(); onIterationChange?.(node.id!, iterationInfo.current - 1); }}
              disabled={iterationInfo.current <= 0}
            >
              &lsaquo;
            </button>
            <span>{iterationInfo.current + 1} / {iterationInfo.total}</span>
            <button
              onClick={(e) => { e.stopPropagation(); onIterationChange?.(node.id!, iterationInfo.current + 1); }}
              disabled={iterationInfo.current >= iterationInfo.total - 1}
            >
              &rsaquo;
            </button>
          </div>
        </foreignObject>
      )}

      {renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
      {renderChildren(node, absX, absY, childProps)}
    </g>
  );
}

/** Render internal edges adjusted for compound coordinates */
function renderInternalEdges(
  internalEdges: DiagramEdgeType[],
  absX: number, absY: number,
  executionOverlay?: Map<string, NodeExecutionState>,
) {
  return (
    <g className="edges">
      {internalEdges.map((edge, i) => {
        const isTraversed = executionOverlay
          ? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId))
          : undefined;
        return (
          <DiagramEdge
            key={`${edge.sourceId}-${edge.targetId}-${i}`}
            edge={{
              ...edge,
              points: edge.points.map(p => [p[0] - absX, p[1] - absY]),
            }}
            traversed={isTraversed}
          />
        );
      })}
    </g>
  );
}

/** Render children — compounds recurse, leaves become DiagramNode */
function renderChildren(
  node: DiagramNodeType,
  absX: number, absY: number,
  props: Omit<CompoundNodeProps, 'node' | 'parentX' | 'parentY'>,
) {
  return (
    <>
      {node.children?.map(child => {
        if (isCompoundType(child.type) && child.children && child.children.length > 0) {
          return (
            <CompoundNode
              key={child.id}
              node={child}
              parentX={absX}
              parentY={absY}
              {...props}
            />
          );
        }
        return (
          <DiagramNode
            key={child.id}
            node={{
              ...child,
              x: (child.x ?? 0) - absX,
              y: (child.y ?? 0) - absY,
            }}
            isHovered={props.hoveredNodeId === child.id}
            isSelected={props.selectedNodeId === child.id}
            config={child.id ? props.nodeConfigs?.get(child.id) : undefined}
            executionState={props.executionOverlay?.get(child.id ?? '')}
            overlayActive={props.overlayActive}
            heatmapEntry={child.id ? props.latencyHeatmap?.get(child.id) : undefined}
            onClick={() => child.id && props.onNodeClick(child.id)}
            onDoubleClick={() => child.id && props.onNodeDoubleClick?.(child.id)}
            onMouseEnter={() => child.id && props.onNodeEnter(child.id)}
            onMouseLeave={props.onNodeLeave}
          />
        );
      })}
    </>
  );
}

function collectIds(nodes: DiagramNodeType[], set: Set<string>) {
  for (const n of nodes) {
    if (n.id) set.add(n.id);
    if (n.children) collectIds(n.children, set);
  }
}

function hasExecutedDescendant(
  node: DiagramNodeType,
  overlay?: Map<string, NodeExecutionState>,
): boolean {
  if (!overlay || !node.children) return false;
  for (const child of node.children) {
    if (child.id && overlay.has(child.id)) return true;
    if (child.children && hasExecutedDescendant(child, overlay)) return true;
  }
  return false;
}

function hasFailedDescendant(
  node: DiagramNodeType,
  overlay?: Map<string, NodeExecutionState>,
): boolean {
  if (!overlay || !node.children) return false;
  for (const child of node.children) {
    if (child.id) {
      const state = overlay.get(child.id);
      if (state?.status === 'FAILED') return true;
    }
    if (child.children && hasFailedDescendant(child, overlay)) return true;
  }
  return false;
}
