/**
 * Automation2 Flow Editor - Graph helpers for data flow (ancestors, topo order).
 */

import type { CanvasNode, CanvasConnection } from '../../editor/FlowCanvas';

/** Build reverse adjacency: targetId -> sourceId[] */
function buildReverseAdjacency(connections: CanvasConnection[]): Record<string, string[]> {
  const rev: Record<string, string[]> = {};
  for (const c of connections) {
    if (!rev[c.targetId]) rev[c.targetId] = [];
    rev[c.targetId].push(c.sourceId);
  }
  return rev;
}

/** BFS backward from node to collect all ancestor node IDs */
export function getAncestorNodeIds(
  currentNodeId: string,
  nodes: CanvasNode[],
  connections: CanvasConnection[]
): string[] {
  const nodeIds = new Set(nodes.map((n) => n.id));
  const rev = buildReverseAdjacency(connections);
  const result = new Set<string>();
  const queue = [currentNodeId];
  const visited = new Set<string>([currentNodeId]);

  while (queue.length > 0) {
    const nid = queue.shift()!;
    const sources = rev[nid] ?? [];
    for (const src of sources) {
      if (!visited.has(src) && nodeIds.has(src)) {
        visited.add(src);
        result.add(src);
        queue.push(src);
      }
    }
  }
  return Array.from(result);
}

/** Topological order: triggers first, then BFS by connections (mirrors backend topoSort) */
export function topologicalOrder(
  nodes: CanvasNode[],
  connections: CanvasConnection[]
): CanvasNode[] {
  const nodeById = new Map(nodes.map((n) => [n.id, n]));
  const triggers = nodes.filter((n) => n.type.startsWith('trigger.'));
  if (triggers.length === 0) return [...nodes];

  const fwd: Record<string, string[]> = {};
  for (const c of connections) {
    if (!fwd[c.sourceId]) fwd[c.sourceId] = [];
    fwd[c.sourceId].push(c.targetId);
  }

  const order: CanvasNode[] = [];
  const visited = new Set<string>();

  const q = [...triggers.map((t) => t.id)];
  for (const tid of triggers.map((t) => t.id)) {
    if (!visited.has(tid)) {
      visited.add(tid);
      const n = nodeById.get(tid);
      if (n) order.push(n);
    }
  }

  let i = 0;
  while (i < q.length) {
    const nid = q[i++];
    const targets = fwd[nid] ?? [];
    for (const tgt of targets) {
      if (!visited.has(tgt)) {
        visited.add(tgt);
        q.push(tgt);
        const n = nodeById.get(tgt);
        if (n) order.push(n);
      }
    }
  }

  for (const n of nodes) {
    if (n.id && !visited.has(n.id)) order.push(n);
  }
  return order;
}

/** Node IDs that are valid sources for the current node (ancestors in DAG) */
export function getAvailableSources(
  currentNodeId: string,
  nodes: CanvasNode[],
  connections: CanvasConnection[]
): string[] {
  return getAncestorNodeIds(currentNodeId, nodes, connections);
}
