/**
 * Span Visualizer
 * Reconstructs span trees from logs for debugging distributed traces
 */

import type { Span } from '@/types/tracing';

export interface SpanTree {
  span: Span;
  children: SpanTree[];
  totalDuration: number;
  selfDuration: number;
}

/**
 * Build span tree from flat span logs
 */
export function buildSpanTree(spans: Span[]): SpanTree | null {
  const spanMap = new Map<string, Span>();
  const childrenMap = new Map<string, Span[]>();
  
  // Index spans
  for (const span of spans) {
    spanMap.set(span.spanId, span);
    
    if (span.parentSpanId) {
      if (!childrenMap.has(span.parentSpanId)) {
        childrenMap.set(span.parentSpanId, []);
      }
      childrenMap.get(span.parentSpanId)!.push(span);
    }
  }
  
  // Find root span
  const rootSpan = spans.find(s => !s.parentSpanId);
  if (!rootSpan) return null;
  
  // Build tree recursively
  function buildTree(span: Span): SpanTree {
    const children = childrenMap.get(span.spanId) || [];
    const childTrees = children.map(buildTree);
    
    const totalDuration = span.duration || 0;
    const childrenDuration = childTrees.reduce((sum, child) => sum + child.totalDuration, 0);
    const selfDuration = totalDuration - childrenDuration;
    
    return {
      span,
      children: childTrees,
      totalDuration,
      selfDuration,
    };
  }
  
  return buildTree(rootSpan);
}

/**
 * Format span tree as ASCII art
 */
export function formatSpanTree(tree: SpanTree, indent: number = 0): string {
  const prefix = '  '.repeat(indent);
  const status = tree.span.status === 'error' ? '❌' : tree.span.status === 'ok' ? '✅' : '⏳';
  const line = `${prefix}${status} ${tree.span.name} (${tree.span.duration}ms / self: ${tree.selfDuration}ms)`;
  
  const childLines = tree.children.map(child => formatSpanTree(child, indent + 1));
  
  return [line, ...childLines].join('\n');
}

/**
 * Calculate span statistics for a tree
 */
export function calculateSpanStats(tree: SpanTree): {
  totalSpans: number;
  errorCount: number;
  maxDepth: number;
  totalDuration: number;
  criticalPath: string[];
} {
  let totalSpans = 0;
  let errorCount = 0;
  let maxDepth = 0;
  
  function traverse(node: SpanTree, depth: number) {
    totalSpans++;
    if (node.span.status === 'error') errorCount++;
    maxDepth = Math.max(maxDepth, depth);
    
    node.children.forEach(child => traverse(child, depth + 1));
  }
  
  traverse(tree, 0);
  
  // Find critical path (longest duration path)
  function findCriticalPath(node: SpanTree): string[] {
    if (node.children.length === 0) {
      return [node.span.name];
    }
    
    const longestChild = node.children.reduce((longest, child) => 
      child.totalDuration > longest.totalDuration ? child : longest
    );
    
    return [node.span.name, ...findCriticalPath(longestChild)];
  }
  
  return {
    totalSpans,
    errorCount,
    maxDepth,
    totalDuration: tree.totalDuration,
    criticalPath: findCriticalPath(tree),
  };
}

/**
 * Extract all events from a span tree
 */
export function extractAllEvents(tree: SpanTree): Array<{
  spanName: string;
  eventName: string;
  timestamp: number;
  attributes?: Record<string, unknown>;
}> {
  const events: Array<{
    spanName: string;
    eventName: string;
    timestamp: number;
    attributes?: Record<string, unknown>;
  }> = [];
  
  function traverse(node: SpanTree) {
    node.span.events.forEach(event => {
      events.push({
        spanName: node.span.name,
        eventName: event.name,
        timestamp: event.timestamp,
        attributes: event.attributes,
      });
    });
    
    node.children.forEach(child => traverse(child));
  }
  
  traverse(tree);
  
  // Sort by timestamp
  return events.sort((a, b) => a.timestamp - b.timestamp);
}
