import { useCallback, useEffect, useState } from 'react';
import type { ProcessDiagramProps } from './types';
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
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 { Minimap } from './Minimap';
import { isCompoundType } from './node-colors';
import styles from './ProcessDiagram.module.css';

const PADDING = 40;

/** Types that support drill-down — double-click navigates to the target route */
const DRILLDOWN_TYPES = new Set(['DIRECT', 'SEDA']);

/** Extract the target endpoint name from a node's label */
function extractTargetEndpoint(node: DiagramNodeType): string | null {
  // Labels like "to: direct:orderProcessing" or "direct:orderProcessing"
  const label = node.label ?? '';
  const match = label.match(/(?:to:\s*)?(?:direct|seda):(\S+)/i);
  return match ? match[1] : null;
}

/** Convert camelCase to kebab-case: "callGetProduct" → "call-get-product" */
function camelToKebab(s: string): string {
  return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
}

/**
 * Resolve a direct/seda endpoint name to a routeId.
 * Tries: exact match, kebab-case conversion, then gives up.
 */
function resolveRouteId(endpoint: string, knownRouteIds: Set<string>): string | null {
  if (knownRouteIds.has(endpoint)) return endpoint;
  const kebab = camelToKebab(endpoint);
  if (knownRouteIds.has(kebab)) return kebab;
  return null;
}

export function ProcessDiagram({
  application,
  routeId,
  direction = 'LR',
  selectedNodeId,
  onNodeSelect,
  onNodeAction,
  nodeConfigs,
  knownRouteIds,
  className,
  diagramLayout,
  executionOverlay,
  iterationState,
  onIterationChange,
}: ProcessDiagramProps) {
  const overlayActive = !!executionOverlay;
  // Route stack for drill-down navigation
  const [routeStack, setRouteStack] = useState<string[]>([routeId]);

  // Reset stack when the external routeId prop changes
  useEffect(() => {
    setRouteStack([routeId]);
  }, [routeId]);

  const currentRouteId = routeStack[routeStack.length - 1];

  const { sections, totalWidth, totalHeight, isLoading, error } = useDiagramData(
    application, currentRouteId, direction, diagramLayout,
  );

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

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

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

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

  const handleNodeDoubleClick = useCallback(
    (nodeId: string) => {
      const node = findNodeById(sections, nodeId);
      if (!node || !DRILLDOWN_TYPES.has(node.type ?? '')) return;
      const endpoint = extractTargetEndpoint(node);
      if (!endpoint) return;
      const resolved = knownRouteIds
        ? resolveRouteId(endpoint, knownRouteIds)
        : endpoint;
      if (resolved) {
        onNodeSelect?.('');
        setRouteStack(prev => [...prev, resolved]);
      }
    },
    [sections, onNodeSelect, knownRouteIds],
  );

  const handleBreadcrumbClick = useCallback(
    (index: number) => {
      onNodeSelect?.('');
      setRouteStack(prev => prev.slice(0, index + 1));
    },
    [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') {
        if (routeStack.length > 1) {
          // Go back one level
          setRouteStack(prev => prev.slice(0, -1));
        } else {
          onNodeSelect?.('');
        }
        return;
      }
      zoom.onKeyDown(e, contentWidth, contentHeight);
    },
    [onNodeSelect, zoom, contentWidth, contentHeight, routeStack.length],
  );

  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 handlerSections = sections.slice(1);

  return (
    <div
      ref={zoom.containerRef}
      className={`${styles.container} ${className ?? ''}`}
    >
      {/* Breadcrumb bar — only shown when drilled down */}
      {routeStack.length > 1 && (
        <div className={styles.breadcrumbs}>
          {routeStack.map((route, i) => (
            <span key={i} className={styles.breadcrumbItem}>
              {i > 0 && <span className={styles.breadcrumbSep}>/</span>}
              {i < routeStack.length - 1 ? (
                <button
                  className={styles.breadcrumbLink}
                  onClick={() => handleBreadcrumbClick(i)}
                >
                  {route}
                </button>
              ) : (
                <span className={styles.breadcrumbCurrent}>{route}</span>
              )}
            </span>
          ))}
        </div>
      )}

      <svg
        ref={zoom.svgRef}
        className={styles.svg}
        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>
          <marker
            id="arrowhead-green"
            markerWidth="8"
            markerHeight="6"
            refX="7"
            refY="3"
            orient="auto"
          >
            <polygon points="0 0, 8 3, 0 6" fill="#3D7C47" />
          </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) => {
              const isTraversed = executionOverlay
                ? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId))
                : undefined;
              return (
                <DiagramEdge key={`${edge.sourceId}-${edge.targetId}-${i}`} edge={edge} traversed={isTraversed} />
              );
            })}
          </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}
                    executionOverlay={executionOverlay}
                    overlayActive={overlayActive}
                    iterationState={iterationState}
                    onIterationChange={onIterationChange}
                    onNodeClick={handleNodeClick}
                    onNodeDoubleClick={handleNodeDoubleClick}
                    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}
                  executionState={executionOverlay?.get(node.id ?? '')}
                  overlayActive={overlayActive}
                  onClick={() => node.id && handleNodeClick(node.id)}
                  onDoubleClick={() => node.id && handleNodeDoubleClick(node.id)}
                  onMouseEnter={() => node.id && toolbar.onNodeEnter(node.id)}
                  onMouseLeave={toolbar.onNodeLeave}
                />
              );
            })}
          </g>

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

          {/* Handler sections (completion, then error) */}
          {handlerSections.map((section, i) => (
            <ErrorSection
              key={`handler-${i}`}
              section={section}
              totalWidth={totalWidth}
              selectedNodeId={selectedNodeId}
              hoveredNodeId={toolbar.hoveredNodeId}
              nodeConfigs={nodeConfigs}
              executionOverlay={executionOverlay}
              overlayActive={overlayActive}
              iterationState={iterationState}
              onIterationChange={onIterationChange}
              onNodeClick={handleNodeClick}
              onNodeDoubleClick={handleNodeDoubleClick}
              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;
        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}
          />
        );
      })()}

      <Minimap
        sections={sections}
        totalWidth={totalWidth}
        totalHeight={totalHeight}
        scale={zoom.state.scale}
        translateX={zoom.state.translateX}
        translateY={zoom.state.translateY}
        containerWidth={zoom.containerRef.current?.clientWidth ?? 0}
        containerHeight={zoom.containerRef.current?.clientHeight ?? 0}
        onPan={zoom.panTo}
      />

      <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,
): DiagramNodeType | 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: DiagramNodeType[],
  nodeId: string,
): DiagramNodeType | 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;
}

function topLevelEdge(
  edge: import('../../api/queries/diagrams').DiagramEdge,
  nodes: DiagramNodeType[],
): boolean {
  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: DiagramNodeType[], set: Set<string>) {
  for (const n of nodes) {
    if (n.id) set.add(n.id);
    if (n.children) collectDescendantIds(n.children, set);
  }
}
