/**
 * Data Transformation Helpers
 *
 * Functions for layout calculations, step numbering, and node positioning
 * in the flow builder canvas.
 */

import { WorkflowReactFlowNode as WorkflowReactFlowNodeType } from '../types/reactflow';
import { Edge } from '@xyflow/react';

// Layout constants
export const VERTICAL_SPACING = 220;
export const CENTER_X = 250;
const START_Y = 80;
export const NODE_SPACING = 180;
export const LANE_WIDTH = 350;

/**
 * Calculate step numbers for nodes based on edge connections
 * Returns a map of nodeId -> stepNumber (1-based)
 */
export function calculateStepNumbers(
  nodes: WorkflowReactFlowNodeType[],
  edges: Edge[],
): Map<string, number> {
  const stepNumbers = new Map<string, number>();

  // Find the trigger node (always step 1)
  const triggerNode = nodes.find((n) => n.type === 'trigger');
  if (!triggerNode) return stepNumbers;

  stepNumbers.set(triggerNode.id, 1);

  // Build adjacency list from edges
  const adjacency = new Map<string, string[]>();
  edges.forEach((edge) => {
    const targets = adjacency.get(edge.source) || [];
    targets.push(edge.target);
    adjacency.set(edge.source, targets);
  });

  // BFS to assign step numbers
  const queue: string[] = [triggerNode.id];
  const visited = new Set<string>([triggerNode.id]);

  while (queue.length > 0) {
    const currentId = queue.shift()!;
    const currentStep = stepNumbers.get(currentId)!;
    const targets = adjacency.get(currentId) || [];

    targets.forEach((targetId) => {
      if (!visited.has(targetId)) {
        visited.add(targetId);
        stepNumbers.set(targetId, currentStep + 1);
        queue.push(targetId);
      }
    });
  }

  // Handle disconnected nodes - assign them step numbers based on position
  nodes.forEach((node, index) => {
    if (!stepNumbers.has(node.id)) {
      stepNumbers.set(node.id, index + 1);
    }
  });

  return stepNumbers;
}

/**
 * Auto-positions nodes using a BFS layout that supports branching.
 *
 * When edges are provided, the algorithm walks the graph from the trigger node:
 * - Single-child nodes place their child directly below (same x).
 * - Multi-child nodes (e.g. Router) fan children out horizontally, centered
 *   around the parent's x position.
 * - Each branch then continues vertically from its starting position.
 *
 * Falls back to simple vertical column layout when no edges are supplied
 * (backwards compatible).
 */
export function autoLayoutNodes(
  nodes: WorkflowReactFlowNodeType[],
  edges: Edge[] = [],
): WorkflowReactFlowNodeType[] {
  if (nodes.length === 0) return nodes;

  // Fallback: simple vertical column (backwards compatible when no edges)
  if (edges.length === 0) {
    const sortedNodes = [...nodes].sort((a, b) => {
      if (a.type === 'trigger') return -1;
      if (b.type === 'trigger') return 1;
      const stepA = a.data.stepNumber || Infinity;
      const stepB = b.data.stepNumber || Infinity;
      return stepA - stepB;
    });

    return sortedNodes.map((node, index) => ({
      ...node,
      position: {
        x: CENTER_X,
        y: START_Y + index * VERTICAL_SPACING,
      },
    }));
  }

  // Build adjacency list (source -> targets)
  const adjacency = new Map<string, string[]>();
  edges.forEach((edge) => {
    const targets = adjacency.get(edge.source) || [];
    targets.push(edge.target);
    adjacency.set(edge.source, targets);
  });

  const positions = new Map<string, { x: number; y: number }>();
  const triggerNode = nodes.find((n) => n.type === 'trigger');

  if (!triggerNode) {
    // No trigger — fallback to vertical column
    return nodes.map((node, index) => ({
      ...node,
      position: { x: CENTER_X, y: START_Y + index * VERTICAL_SPACING },
    }));
  }

  // BFS from trigger
  positions.set(triggerNode.id, { x: CENTER_X, y: START_Y });
  const queue: string[] = [triggerNode.id];
  const visited = new Set<string>([triggerNode.id]);

  while (queue.length > 0) {
    const currentId = queue.shift()!;
    const currentPos = positions.get(currentId)!;
    const children = adjacency.get(currentId) || [];

    if (children.length === 1) {
      // Single child — directly below
      const childId = children[0];
      if (!visited.has(childId)) {
        visited.add(childId);
        positions.set(childId, {
          x: currentPos.x,
          y: currentPos.y + VERTICAL_SPACING,
        });
        queue.push(childId);
      }
    } else if (children.length > 1) {
      // Multiple children (branching) — fan out horizontally
      const totalWidth = (children.length - 1) * LANE_WIDTH;
      const startX = currentPos.x - totalWidth / 2;

      children.forEach((childId, i) => {
        if (!visited.has(childId)) {
          visited.add(childId);
          positions.set(childId, {
            x: startX + i * LANE_WIDTH,
            y: currentPos.y + VERTICAL_SPACING,
          });
          queue.push(childId);
        }
      });
    }
  }

  // Handle disconnected nodes (not reached by BFS) — stack below
  let fallbackY = START_Y + positions.size * VERTICAL_SPACING;
  nodes.forEach((node) => {
    if (!positions.has(node.id)) {
      positions.set(node.id, { x: CENTER_X, y: fallbackY });
      fallbackY += VERTICAL_SPACING;
    }
  });

  return nodes.map((node) => ({
    ...node,
    position: positions.get(node.id) || { x: CENTER_X, y: START_Y },
  }));
}
