import type { DiagramNode as DiagramNodeType, DiagramEdge as DiagramEdgeType } from '../../api/queries/diagrams';
import type { NodeConfig } from './types';
import type { NodeExecutionState, IterationInfo } from '../ExecutionDiagram/types';
import { colorForType, isCompoundType } 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;
  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,
  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,
    onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave,
  };

  // _TRY_BODY: transparent wrapper — no header, no border, just layout
  if (node.type === '_TRY_BODY') {
    return (
      <g transform={`translate(${x}, ${y})`}>
        {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})`}>
        {/* 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, etc.)
  return (
    <g data-node-id={node.id} transform={`translate(${x}, ${y})`}>
      {/* Container body */}
      <rect
        x={0}
        y={0}
        width={w}
        height={h}
        rx={CORNER_RADIUS}
        fill="white"
        stroke={color}
        strokeWidth={1.5}
      />

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

      {/* Header label */}
      <text
        x={w / 2}
        y={HEADER_HEIGHT / 2 + 4}
        fill="white"
        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}
            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);
  }
}
