/**
 * AeThex Visual Scripting - Code Generator
 * Converts visual script nodes into platform-specific code
 */

import { Node, Edge } from 'reactflow';
import {
  getNodeDefinition,
  NodeDefinition,
  PortType,
  ALL_NODES
} from './node-definitions';

export type Platform = 'roblox' | 'uefn' | 'spatial';

export interface GenerationResult {
  success: boolean;
  code: string;
  errors: string[];
  warnings: string[];
  nodeCount: number;
  connectionCount: number;
}

export interface NodeData {
  type: string;
  label: string;
  values: Record<string, any>;
}

// Track visited nodes to prevent infinite loops
type VisitedSet = Set<string>;

/**
 * Generate code from visual script nodes
 */
export function generateCode(
  nodes: Node<NodeData>[],
  edges: Edge[],
  platform: Platform
): GenerationResult {
  const result: GenerationResult = {
    success: true,
    code: '',
    errors: [],
    warnings: [],
    nodeCount: nodes.length,
    connectionCount: edges.length,
  };

  if (nodes.length === 0) {
    result.code = getEmptyTemplate(platform);
    return result;
  }

  // Find all event nodes (entry points)
  const eventNodes = nodes.filter(n => {
    const def = getNodeDefinition(n.data.type);
    return def?.category === 'events';
  });

  if (eventNodes.length === 0) {
    result.warnings.push('No event nodes found. Add an event node to start your script.');
    result.code = getEmptyTemplate(platform);
    return result;
  }

  // Build adjacency map for traversal
  const adjacencyMap = buildAdjacencyMap(edges);
  const reverseAdjacencyMap = buildReverseAdjacencyMap(edges);

  // Generate code for each event node
  const codeBlocks: string[] = [];

  for (const eventNode of eventNodes) {
    try {
      const visited: VisitedSet = new Set();
      const blockCode = generateNodeCode(
        eventNode,
        nodes,
        adjacencyMap,
        reverseAdjacencyMap,
        platform,
        visited,
        0
      );
      codeBlocks.push(blockCode);
    } catch (error: any) {
      result.errors.push(`Error in node "${eventNode.data.label}": ${error.message}`);
      result.success = false;
    }
  }

  // Combine code blocks
  result.code = wrapCode(codeBlocks.join('\n\n'), platform);

  return result;
}

/**
 * Build adjacency map from edges (source -> targets)
 */
function buildAdjacencyMap(edges: Edge[]): Map<string, Edge[]> {
  const map = new Map<string, Edge[]>();

  for (const edge of edges) {
    const key = `${edge.source}.${edge.sourceHandle}`;
    if (!map.has(key)) {
      map.set(key, []);
    }
    map.get(key)!.push(edge);
  }

  return map;
}

/**
 * Build reverse adjacency map (target -> sources)
 */
function buildReverseAdjacencyMap(edges: Edge[]): Map<string, Edge[]> {
  const map = new Map<string, Edge[]>();

  for (const edge of edges) {
    const key = `${edge.target}.${edge.targetHandle}`;
    if (!map.has(key)) {
      map.set(key, []);
    }
    map.get(key)!.push(edge);
  }

  return map;
}

/**
 * Generate code for a single node and its connected nodes
 */
function generateNodeCode(
  node: Node<NodeData>,
  allNodes: Node<NodeData>[],
  adjacencyMap: Map<string, Edge[]>,
  reverseMap: Map<string, Edge[]>,
  platform: Platform,
  visited: VisitedSet,
  depth: number
): string {
  if (visited.has(node.id)) {
    return '-- [Circular reference detected]';
  }
  visited.add(node.id);

  const definition = getNodeDefinition(node.data.type);
  if (!definition) {
    return `-- Unknown node type: ${node.data.type}`;
  }

  const template = definition.codeTemplate[platform];
  if (!template) {
    return `-- Node "${definition.label}" not supported on ${platform}`;
  }

  // Resolve input values
  const resolvedValues: Record<string, string> = {};

  for (const input of definition.inputs) {
    // Check if there's a connection to this input
    const connectionKey = `${node.id}.${input.id}`;
    const incomingEdges = reverseMap.get(connectionKey) || [];

    if (incomingEdges.length > 0 && input.type !== 'flow') {
      // Get value from connected node
      const sourceEdge = incomingEdges[0];
      const sourceNode = allNodes.find(n => n.id === sourceEdge.source);

      if (sourceNode) {
        resolvedValues[input.id] = generateValueExpression(
          sourceNode,
          sourceEdge.sourceHandle || 'value',
          allNodes,
          reverseMap,
          platform,
          new Set(visited),
          depth + 1
        );
      }
    } else {
      // Use node's stored value or default
      const value = node.data.values?.[input.id] ?? input.defaultValue ?? getDefaultForType(input.type);
      resolvedValues[input.id] = formatValue(value, input.type, platform);
    }
  }

  // Generate body code (connected flow outputs)
  let code = template;

  // Replace placeholders with values
  for (const [key, value] of Object.entries(resolvedValues)) {
    code = code.replace(new RegExp(`{{${key}}}`, 'g'), value);
  }

  // Handle flow outputs (execution continues)
  for (const output of definition.outputs) {
    if (output.type === 'flow') {
      const connectionKey = `${node.id}.${output.id}`;
      const outgoingEdges = adjacencyMap.get(connectionKey) || [];

      let bodyCode = '';
      for (const edge of outgoingEdges) {
        const targetNode = allNodes.find(n => n.id === edge.target);
        if (targetNode) {
          bodyCode += generateNodeCode(
            targetNode,
            allNodes,
            adjacencyMap,
            reverseMap,
            platform,
            visited,
            depth + 1
          );
        }
      }

      // Replace body placeholder
      const bodyPlaceholder = getBodyPlaceholder(output.id);
      code = code.replace(new RegExp(bodyPlaceholder, 'g'), indent(bodyCode, depth + 1));
    }
  }

  // Clean up any remaining placeholders
  code = code.replace(/{{[A-Z_]+_BODY}}/g, '');
  code = code.replace(/{{BODY}}/g, '');

  return code;
}

/**
 * Generate a value expression for a data node
 */
function generateValueExpression(
  node: Node<NodeData>,
  outputHandle: string,
  allNodes: Node<NodeData>[],
  reverseMap: Map<string, Edge[]>,
  platform: Platform,
  visited: VisitedSet,
  depth: number
): string {
  if (visited.has(node.id)) {
    return 'nil';
  }
  visited.add(node.id);

  const definition = getNodeDefinition(node.data.type);
  if (!definition) {
    return 'nil';
  }

  const template = definition.codeTemplate[platform];
  if (!template) {
    return 'nil';
  }

  // Resolve input values recursively
  let code = template;

  for (const input of definition.inputs) {
    const connectionKey = `${node.id}.${input.id}`;
    const incomingEdges = reverseMap.get(connectionKey) || [];

    let value: string;
    if (incomingEdges.length > 0 && input.type !== 'flow') {
      const sourceEdge = incomingEdges[0];
      const sourceNode = allNodes.find(n => n.id === sourceEdge.source);

      if (sourceNode) {
        value = generateValueExpression(
          sourceNode,
          sourceEdge.sourceHandle || 'value',
          allNodes,
          reverseMap,
          platform,
          visited,
          depth + 1
        );
      } else {
        value = formatValue(input.defaultValue, input.type, platform);
      }
    } else {
      const nodeValue = node.data.values?.[input.id] ?? input.defaultValue ?? getDefaultForType(input.type);
      value = formatValue(nodeValue, input.type, platform);
    }

    code = code.replace(new RegExp(`{{${input.id}}}`, 'g'), value);
  }

  // Handle special operations
  if (node.data.type === 'math') {
    const op = node.data.values?.operation || 'add';
    const opSymbol = getOperationSymbol(op);
    code = code.replace('{{OPERATION}}', opSymbol);
  }

  if (node.data.type === 'compare') {
    const comp = node.data.values?.comparison || 'equals';
    const compSymbol = getComparisonSymbol(comp, platform);
    code = code.replace('{{COMPARISON}}', compSymbol);
  }

  return code;
}

/**
 * Format a value for the target platform
 */
function formatValue(value: any, type: PortType, platform: Platform): string {
  if (value === null || value === undefined) {
    return getDefaultForType(type);
  }

  switch (type) {
    case 'string':
      return `"${String(value).replace(/"/g, '\\"')}"`;
    case 'number':
      return String(Number(value) || 0);
    case 'boolean':
      if (platform === 'roblox') {
        return value ? 'true' : 'false';
      } else if (platform === 'uefn') {
        return value ? 'true' : 'false';
      }
      return value ? 'true' : 'false';
    default:
      return String(value);
  }
}

/**
 * Get default value for a port type
 */
function getDefaultForType(type: PortType): string {
  switch (type) {
    case 'number': return '0';
    case 'string': return '""';
    case 'boolean': return 'false';
    case 'object': return 'nil';
    case 'array': return '{}';
    default: return 'nil';
  }
}

/**
 * Get the body placeholder name for an output
 */
function getBodyPlaceholder(outputId: string): string {
  const mapping: Record<string, string> = {
    'flow': '{{BODY}}',
    'true': '{{TRUE_BODY}}',
    'false': '{{FALSE_BODY}}',
    'loop': '{{LOOP_BODY}}',
    'complete': '{{COMPLETE_BODY}}',
  };
  return mapping[outputId] || '{{BODY}}';
}

/**
 * Get math operation symbol
 */
function getOperationSymbol(operation: string): string {
  const symbols: Record<string, string> = {
    'add': '+',
    'subtract': '-',
    'multiply': '*',
    'divide': '/',
    'modulo': '%',
    'power': '^',
  };
  return symbols[operation] || '+';
}

/**
 * Get comparison symbol for platform
 */
function getComparisonSymbol(comparison: string, platform: Platform): string {
  const symbols: Record<string, string> = {
    'equals': '==',
    'notEquals': platform === 'roblox' ? '~=' : '!=',
    'greater': '>',
    'less': '<',
    'greaterEqual': '>=',
    'lessEqual': '<=',
  };
  return symbols[comparison] || '==';
}

/**
 * Indent code block
 */
function indent(code: string, level: number): string {
  const tab = '\t';
  const prefix = tab.repeat(level);
  return code
    .split('\n')
    .map(line => line.trim() ? prefix + line : line)
    .join('\n');
}

/**
 * Wrap generated code with platform boilerplate
 */
function wrapCode(code: string, platform: Platform): string {
  switch (platform) {
    case 'roblox':
      return `-- Generated by AeThex Visual Scripting
-- Platform: Roblox (Lua)

${code}`;

    case 'uefn':
      return `# Generated by AeThex Visual Scripting
# Platform: UEFN (Verse)

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

aethex_generated_device := class(creative_device):

${indent(code, 1)}`;

    case 'spatial':
      return `// Generated by AeThex Visual Scripting
// Platform: Spatial (TypeScript)

import { SpaceService, World } from '@spatial/core';

${code}`;

    default:
      return code;
  }
}

/**
 * Get empty template for a platform
 */
function getEmptyTemplate(platform: Platform): string {
  switch (platform) {
    case 'roblox':
      return `-- Generated by AeThex Visual Scripting
-- Add event nodes to start building your script!

-- Example:
-- 1. Drag an "On Player Join" event node
-- 2. Connect it to an action like "Print"
-- 3. Click "Generate Code"

print("Hello from AeThex!")`;

    case 'uefn':
      return `# Generated by AeThex Visual Scripting
# Add event nodes to start building your script!

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

aethex_generated_device := class(creative_device):
    OnBegin<override>(): void =
        Print("Hello from AeThex!")`;

    case 'spatial':
      return `// Generated by AeThex Visual Scripting
// Add event nodes to start building your script!

import { SpaceService } from '@spatial/core';

SpaceService.onSpaceReady.on(() => {
  console.log("Hello from AeThex!");
});`;

    default:
      return '// No code generated';
  }
}

/**
 * Validate visual script for errors
 */
export function validateScript(
  nodes: Node<NodeData>[],
  edges: Edge[],
  platform: Platform
): { valid: boolean; errors: string[]; warnings: string[] } {
  const errors: string[] = [];
  const warnings: string[] = [];

  // Check for event nodes
  const eventNodes = nodes.filter(n => {
    const def = getNodeDefinition(n.data.type);
    return def?.category === 'events';
  });

  if (eventNodes.length === 0) {
    warnings.push('No event nodes found. Your script needs at least one event to run.');
  }

  // Check for disconnected nodes
  const connectedNodes = new Set<string>();
  for (const edge of edges) {
    connectedNodes.add(edge.source);
    connectedNodes.add(edge.target);
  }

  for (const node of nodes) {
    if (!connectedNodes.has(node.id) && eventNodes.findIndex(e => e.id === node.id) === -1) {
      const def = getNodeDefinition(node.data.type);
      if (def?.category !== 'events') {
        warnings.push(`Node "${node.data.label || node.data.type}" is not connected.`);
      }
    }
  }

  // Check for required inputs
  for (const node of nodes) {
    const def = getNodeDefinition(node.data.type);
    if (!def) continue;

    for (const input of def.inputs) {
      if (input.required && input.type !== 'flow') {
        const hasConnection = edges.some(
          e => e.target === node.id && e.targetHandle === input.id
        );
        const hasValue = node.data.values?.[input.id] !== undefined;

        if (!hasConnection && !hasValue) {
          errors.push(`Node "${node.data.label || def.label}" is missing required input: ${input.name}`);
        }
      }
    }
  }

  // Check for platform compatibility
  for (const node of nodes) {
    const def = getNodeDefinition(node.data.type);
    if (def && !def.platforms.includes(platform)) {
      errors.push(`Node "${def.label}" is not available on ${platform}`);
    }
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}
