import { useCallback, useEffect } from 'react';
import type { ProcessDiagramProps } from './types';
import { useDiagramData } from './useDiagramData';
import { useZoomPan } from './useZoomPan';
import { useToolbarHover, NodeToolbar } from './NodeToolbar';
import { DiagramNode } from './DiagramNode';
import { DiagramEdge } from './DiagramEdge';
import { CompoundNode } from './CompoundNode';
import { ErrorSection } from './ErrorSection';
import { ZoomControls } from './ZoomControls';
import { isCompoundType } from './node-colors';
import styles from './ProcessDiagram.module.css';

const PADDING = 40;

export function ProcessDiagram({
  application,
  routeId,
  direction = 'LR',
  selectedNodeId,
  onNodeSelect,
  onNodeAction,
  nodeConfigs,
  className,
}: ProcessDiagramProps) {
  const { sections, totalWidth, totalHeight, isLoading, error } = useDiagramData(
    application, routeId, direction,
  );

  const zoom = useZoomPan();
  const toolbar = useToolbarHover();

  const contentWidth = totalWidth + PADDING * 2;
  const contentHeight = totalHeight + PADDING * 2;

  // Reset to 100% at top-left on first data load
  useEffect(() => {
    if (totalWidth > 0 && totalHeight > 0) {
      zoom.resetView();
    }
  }, [totalWidth, totalHeight]); // eslint-disable-line react-hooks/exhaustive-deps

  const handleNodeClick = useCallback(
    (nodeId: string) => { onNodeSelect?.(nodeId); },
    [onNodeSelect],
  );

  const handleNodeAction = useCallback(
    (nodeId: string, action: import('./types').NodeAction) => {
      if (action === 'inspect') onNodeSelect?.(nodeId);
      onNodeAction?.(nodeId, action);
    },
    [onNodeSelect, onNodeAction],
  );

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.key === 'Escape') {
        onNodeSelect?.('');
        return;
      }
      zoom.onKeyDown(e, contentWidth, contentHeight);
    },
    [onNodeSelect, zoom, contentWidth, contentHeight],
  );

  if (isLoading) {
    return (
      <div className={`${styles.container} ${className ?? ''}`}>
        <div className={styles.loading}>Loading diagram...</div>
      </div>
    );
  }

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

  if (sections.length === 0) {
    return (
      <div className={`${styles.container} ${className ?? ''}`}>
        <div className={styles.loading}>No diagram data available</div>
      </div>
    );
  }

  const mainSection = sections[0];
  const errorSections = sections.slice(1);

  return (
    <div
      ref={zoom.containerRef}
      className={`${styles.container} ${className ?? ''}`}
    >
      <svg
        className={styles.svg}
        onWheel={zoom.onWheel}
        onPointerDown={zoom.onPointerDown}
        onPointerMove={zoom.onPointerMove}
        onPointerUp={zoom.onPointerUp}
        onKeyDown={handleKeyDown}
        tabIndex={0}
        onClick={() => onNodeSelect?.('')}
      >
        <defs>
          <marker
            id="arrowhead"
            markerWidth="8"
            markerHeight="6"
            refX="7"
            refY="3"
            orient="auto"
          >
            <polygon points="0 0, 8 3, 0 6" fill="#9CA3AF" />
          </marker>
        </defs>

        <g style={{ transform: zoom.transform, transformOrigin: '0 0' }}>
          {/* Main section top-level edges (not inside compounds) */}
          <g className="edges">
            {mainSection.edges.filter(e => topLevelEdge(e, mainSection.nodes)).map((edge, i) => (
              <DiagramEdge key={`${edge.sourceId}-${edge.targetId}-${i}`} edge={edge} />
            ))}
          </g>

          {/* Main section nodes */}
          <g className="nodes">
            {mainSection.nodes.map(node => {
              if (isCompoundType(node.type) && node.children && node.children.length > 0) {
                return (
                  <CompoundNode
                    key={node.id}
                    node={node}
                    edges={mainSection.edges}
                    selectedNodeId={selectedNodeId}
                    hoveredNodeId={toolbar.hoveredNodeId}
                    nodeConfigs={nodeConfigs}
                    onNodeClick={handleNodeClick}
                    onNodeEnter={toolbar.onNodeEnter}
                    onNodeLeave={toolbar.onNodeLeave}
                  />
                );
              }
              return (
                <DiagramNode
                  key={node.id}
                  node={node}
                  isHovered={toolbar.hoveredNodeId === node.id}
                  isSelected={selectedNodeId === node.id}
                  config={node.id ? nodeConfigs?.get(node.id) : undefined}
                  onClick={() => node.id && handleNodeClick(node.id)}
                  onMouseEnter={() => node.id && toolbar.onNodeEnter(node.id)}
                  onMouseLeave={toolbar.onNodeLeave}
                />
              );
            })}
          </g>

          {/* Toolbar rendered as HTML overlay below */}

          {/* Error handler sections */}
          {errorSections.map((section, i) => (
            <ErrorSection
              key={`error-${i}`}
              section={section}
              totalWidth={totalWidth}
              selectedNodeId={selectedNodeId}
              hoveredNodeId={toolbar.hoveredNodeId}
              nodeConfigs={nodeConfigs}
              onNodeClick={handleNodeClick}
              onNodeEnter={toolbar.onNodeEnter}
              onNodeLeave={toolbar.onNodeLeave}
            />
          ))}
        </g>
      </svg>

      {/* Node toolbar — HTML overlay, fixed size regardless of zoom */}
      {toolbar.hoveredNodeId && onNodeAction && (() => {
        const hNode = findNodeById(sections, toolbar.hoveredNodeId!);
        if (!hNode) return null;
        // Convert SVG coordinates to screen-space using zoom transform
        const nodeCenter = (hNode.x ?? 0) + (hNode.width ?? 160) / 2;
        const nodeTop = hNode.y ?? 0;
        const screenX = nodeCenter * zoom.state.scale + zoom.state.translateX;
        const screenY = nodeTop * zoom.state.scale + zoom.state.translateY;
        return (
          <NodeToolbar
            nodeId={toolbar.hoveredNodeId!}
            screenX={screenX}
            screenY={screenY}
            onAction={handleNodeAction}
            onMouseEnter={toolbar.onToolbarEnter}
            onMouseLeave={toolbar.onToolbarLeave}
          />
        );
      })()}

      <ZoomControls
        onZoomIn={zoom.zoomIn}
        onZoomOut={zoom.zoomOut}
        onFitToView={() => zoom.fitToView(contentWidth, contentHeight)}
        scale={zoom.state.scale}
      />
    </div>
  );
}

function findNodeById(
  sections: import('./types').DiagramSection[],
  nodeId: string,
): import('../../api/queries/diagrams').DiagramNode | undefined {
  for (const section of sections) {
    for (const node of section.nodes) {
      if (node.id === nodeId) return node;
      if (node.children) {
        const found = findInChildren(node.children, nodeId);
        if (found) return found;
      }
    }
  }
  return undefined;
}

function findInChildren(
  nodes: import('../../api/queries/diagrams').DiagramNode[],
  nodeId: string,
): import('../../api/queries/diagrams').DiagramNode | undefined {
  for (const n of nodes) {
    if (n.id === nodeId) return n;
    if (n.children) {
      const found = findInChildren(n.children, nodeId);
      if (found) return found;
    }
  }
  return undefined;
}

/** Returns true if the edge connects two top-level nodes (not inside any compound). */
function topLevelEdge(
  edge: import('../../api/queries/diagrams').DiagramEdge,
  nodes: import('../../api/queries/diagrams').DiagramNode[],
): boolean {
  // Collect all IDs that are children of compound nodes (at any depth)
  const compoundChildIds = new Set<string>();
  for (const n of nodes) {
    if (n.children && n.children.length > 0) {
      collectDescendantIds(n.children, compoundChildIds);
    }
  }
  return !compoundChildIds.has(edge.sourceId) && !compoundChildIds.has(edge.targetId);
}

function collectDescendantIds(
  nodes: import('../../api/queries/diagrams').DiagramNode[],
  set: Set<string>,
) {
  for (const n of nodes) {
    if (n.id) set.add(n.id);
    if (n.children) collectDescendantIds(n.children, set);
  }
}
