import path from 'path';
import { createFileSystem, type FileSystem } from './file-operations';

export interface ImportStatement {
  statement: string;
  line: number;
  from?: string;
  imports?: string[];
  type: 'import' | 'export';
}

export interface DomainNode {
  id: string;
  name: string;
  type: string;
  fileCount: number;
  dependencies: string[];
  dependents: string[];
  files: string[];
}

export interface DependencyEdge {
  from: string;
  to: string;
  type: 'direct-import' | 'indirect-import';
  count: number;
  metadata: {
    files: string[];
  };
}

export interface GraphData {
  nodes: DomainNode[];
  edges: DependencyEdge[];
  circularDependencies: string[][];
  stats: {
    totalDomains: number;
    totalDependencies: number;
    circularDependencyCount: number;
  };
}

export const extractImportStatements = (content: string): ImportStatement[] => {
  const lines = content.split('\n');
  const imports: ImportStatement[] = [];
  
  const importRegex = /^import\s+(?:type\s+)?(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"]([^'"]+)['"];?/;
  const exportRegex = /^export\s+(?:\{[^}]*\}\s+from\s+['"]([^'"]+)['"]|.*from\s+['"]([^'"]+)['"])/;
  
  lines.forEach((line, index) => {
    const trimmedLine = line.trim();
    
    // Import statements
    const importMatch = trimmedLine.match(importRegex);
    if (importMatch) {
      imports.push({
        statement: trimmedLine,
        line: index + 1,
        from: importMatch[1],
        type: 'import'
      });
    }
    
    // Export statements with from
    const exportMatch = trimmedLine.match(exportRegex);
    if (exportMatch) {
      imports.push({
        statement: trimmedLine,
        line: index + 1,
        from: exportMatch[1] || exportMatch[2],
        type: 'export'
      });
    }
  });
  
  return imports;
};

export const extractDomainFromImportPath = (importPath: string, currentDomainPath: string): string | null => {
  // Skip non-relative imports (node_modules, etc.)
  if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
    return null;
  }
  
  // Resolve the absolute path
  const resolvedPath = path.resolve(path.dirname(currentDomainPath), importPath);
  
  // Check if it's a cross-domain import
  const domainMatch = resolvedPath.match(/\/src\/domains\/([^\/]+)/);
  if (domainMatch) {
    const targetDomain = domainMatch[1];
    
    // Extract current domain from current file path
    const currentDomainMatch = currentDomainPath.match(/\/src\/domains\/([^\/]+)/);
    const currentDomain = currentDomainMatch && currentDomainMatch[1] ? currentDomainMatch[1] : null;
    
    // Only return if it's a different domain
    if (targetDomain !== currentDomain) {
      return targetDomain;
    }
  }
  
  return null;
};

export const scanDomainFiles = async (
  fileSystem: FileSystem,
  domainPath: string
): Promise<string[]> => {
  const files: string[] = [];
  
  const scanDirectory = async (dirPath: string): Promise<void> => {
    try {
      const entries = await fileSystem.readdir(dirPath);
      
      for (const entry of entries) {
        const fullPath = path.join(dirPath, entry);
        const stats = await fileSystem.stat(fullPath);
        
        if (stats.isDirectory()) {
          await scanDirectory(fullPath);
        } else if (entry.endsWith('.ts') || entry.endsWith('.tsx')) {
          files.push(fullPath);
        }
      }
    } catch (error) {
      // Directory might not exist or be accessible
    }
  };
  
  await scanDirectory(domainPath);
  return files;
};

export const analyzeDomainDependencies = async (
  fileSystem: FileSystem,
  domainPath: string,
  domainName: string
): Promise<{ dependencies: string[], files: string[] }> => {
  const files = await scanDomainFiles(fileSystem, domainPath);
  const dependencies = new Set<string>();
  
  for (const filePath of files) {
    try {
      const content = await fileSystem.readFile(filePath, 'utf8') as string;
      const imports = extractImportStatements(content);
      
      for (const importStmt of imports) {
        if (importStmt.from) {
          const targetDomain = extractDomainFromImportPath(importStmt.from, filePath);
          if (targetDomain) {
            dependencies.add(targetDomain);
          }
        }
      }
    } catch (error) {
      // File might not be readable
    }
  }
  
  return {
    dependencies: Array.from(dependencies),
    files
  };
};

export const detectCircularDependencies = (nodes: DomainNode[]): string[][] => {
  const cycles: string[][] = [];
  const visited = new Set<string>();
  const recursionStack = new Set<string>();
  
  const dfs = (nodeId: string, path: string[]): void => {
    if (recursionStack.has(nodeId)) {
      // Found a cycle
      const cycleStart = path.indexOf(nodeId);
      if (cycleStart !== -1) {
        cycles.push(path.slice(cycleStart));
      }
      return;
    }
    
    if (visited.has(nodeId)) {
      return;
    }
    
    visited.add(nodeId);
    recursionStack.add(nodeId);
    
    const node = nodes.find(n => n.id === nodeId);
    if (node) {
      for (const dependency of node.dependencies) {
        dfs(dependency, [...path, nodeId]);
      }
    }
    
    recursionStack.delete(nodeId);
  };
  
  for (const node of nodes) {
    if (!visited.has(node.id)) {
      dfs(node.id, []);
    }
  }
  
  return cycles;
};

const determineDomainType = (node: DomainNode, allNodes: DomainNode[]): string => {
  const domainName = node.name.toLowerCase();
  
  // 1. Check explicit naming patterns
  if (domainName.includes('shared') || domainName.includes('common') || domainName.includes('lib')) {
    return 'shared';
  }
  
  if (domainName.includes('core') || domainName.includes('kernel')) {
    return 'core';
  }
  
  // 2. Analyze dependency patterns
  const dependentCount = node.dependents.length;
  const dependencyCount = node.dependencies.length;
  
  // Shared domains: used by many, depend on few
  if (dependentCount >= 3 && dependencyCount <= 1) {
    return 'shared';
  }
  
  // Core domains: used by many, no dependencies on other domains (except shared/lib)
  const nonLibDependencies = node.dependencies.filter(dep => 
    !dep.includes('lib') && !dep.includes('shared') && !dep.includes('common')
  );
  
  if (dependentCount >= 2 && nonLibDependencies.length === 0) {
    return 'core';
  }
  
  // 3. Check for typical core domain names
  const coreDomainNames = ['user', 'auth', 'account', 'identity', 'security'];
  if (coreDomainNames.some(coreName => domainName.includes(coreName))) {
    // Only if it's used by others and doesn't depend on business domains
    if (dependentCount > 0 && nonLibDependencies.length === 0) {
      return 'core';
    }
  }
  
  // Default to feature
  return 'feature';
};

export const generateGraphData = async (
  projectRoot: string,
  fileSystem?: FileSystem
): Promise<GraphData> => {
  const fs = fileSystem || createFileSystem();
  const domainsPath = path.join(projectRoot, 'src', 'domains');
  
  // Check if domains directory exists
  const domainsExist = await fs.exists(domainsPath);
  if (!domainsExist) {
    return {
      nodes: [],
      edges: [],
      circularDependencies: [],
      stats: {
        totalDomains: 0,
        totalDependencies: 0,
        circularDependencyCount: 0
      }
    };
  }
  
  // Get all domain directories
  const domainEntries = await fs.readdir(domainsPath);
  const domainDirs: string[] = [];
  
  for (const entry of domainEntries) {
    const entryPath = path.join(domainsPath, entry);
    const stats = await fs.stat(entryPath);
    if (stats.isDirectory()) {
      domainDirs.push(entry);
    }
  }
  
  // Analyze each domain
  const nodes: DomainNode[] = [];
  const dependencyMap = new Map<string, Set<string>>();
  
  for (const domainName of domainDirs) {
    const domainPath = path.join(domainsPath, domainName);
    const analysis = await analyzeDomainDependencies(fs, domainPath, domainName);
    
    const node: DomainNode = {
      id: domainName,
      name: domainName,
      type: 'feature', // Will be determined later based on dependency analysis
      fileCount: analysis.files.length,
      dependencies: analysis.dependencies,
      dependents: [], // Will be filled later
      files: analysis.files
    };
    
    nodes.push(node);
    dependencyMap.set(domainName, new Set(analysis.dependencies));
  }
  
  // Calculate dependents
  for (const node of nodes) {
    for (const dependency of node.dependencies) {
      const dependentNode = nodes.find(n => n.id === dependency);
      if (dependentNode) {
        dependentNode.dependents.push(node.id);
      }
    }
  }
  
  // Determine domain types based on dependency patterns and naming
  for (const node of nodes) {
    node.type = determineDomainType(node, nodes);
  }
  
  // Generate edges
  const edges: DependencyEdge[] = [];
  const edgeMap = new Map<string, DependencyEdge>();
  
  for (const node of nodes) {
    for (const dependency of node.dependencies) {
      const edgeKey = `${node.id}->${dependency}`;
      
      if (!edgeMap.has(edgeKey)) {
        edgeMap.set(edgeKey, {
          from: node.id,
          to: dependency,
          type: 'direct-import',
          count: 1,
          metadata: {
            files: []
          }
        });
      } else {
        const edge = edgeMap.get(edgeKey)!;
        edge.count++;
      }
    }
  }
  
  edges.push(...edgeMap.values());
  
  // Detect circular dependencies
  const circularDependencies = detectCircularDependencies(nodes);
  
  return {
    nodes,
    edges,
    circularDependencies,
    stats: {
      totalDomains: nodes.length,
      totalDependencies: edges.length,
      circularDependencyCount: circularDependencies.length
    }
  };
}; 