/**
 * Architecture Scanner — regex-based import/require scanning.
 * Builds a dependency graph from source files without needing an AST parser.
 */

import { readFile, readdir, stat } from 'fs/promises'
import { join, relative, dirname, resolve, extname } from 'path'
import type { ArchitectureGraph } from './types.js'

// File extensions to scan
const SCANNABLE_EXTENSIONS = new Set([
  '.ts',
  '.tsx',
  '.js',
  '.jsx',
  '.mts',
  '.mjs',
  '.cjs',
])

// Directories to skip
const SKIP_DIRS = new Set([
  'node_modules',
  '.git',
  '.sandcode',
  '.claude',
  'dist',
  'build',
  'out',
  '.next',
  '.nuxt',
  'coverage',
  'vendor',
  '__pycache__',
])

// Regex patterns for import detection
const IMPORT_PATTERNS = [
  // ES module imports: import ... from '...'
  /import\s+(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g,
  // Dynamic imports: import('...')
  /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
  // CommonJS require: require('...')
  /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
  // Re-exports: export ... from '...'
  /export\s+(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g,
]

/**
 * Recursively collect all scannable source files.
 */
async function collectSourceFiles(
  dir: string,
  rootDir: string,
): Promise<string[]> {
  const files: string[] = []

  let entries
  try {
    entries = await readdir(dir, { withFileTypes: true })
  } catch {
    return files
  }

  for (const entry of entries) {
    if (entry.name.startsWith('.') && SKIP_DIRS.has(entry.name)) continue
    if (SKIP_DIRS.has(entry.name)) continue

    const fullPath = join(dir, entry.name)

    if (entry.isDirectory()) {
      files.push(...(await collectSourceFiles(fullPath, rootDir)))
    } else if (entry.isFile() && SCANNABLE_EXTENSIONS.has(extname(entry.name))) {
      files.push(relative(rootDir, fullPath).replace(/\\/g, '/'))
    }
  }

  return files
}

/**
 * Extract import paths from a source file's content.
 * Returns only relative imports (starting with . or ..).
 */
function extractImports(content: string): string[] {
  const imports: string[] = []

  for (const pattern of IMPORT_PATTERNS) {
    // Reset lastIndex for global regex
    pattern.lastIndex = 0
    let match
    while ((match = pattern.exec(content)) !== null) {
      const importPath = match[1]
      if (importPath && (importPath.startsWith('.') || importPath.startsWith('/'))) {
        imports.push(importPath)
      }
    }
  }

  return [...new Set(imports)]
}

/**
 * Resolve a relative import path to an actual file path.
 * Handles extension resolution (.ts, .tsx, .js, etc.) and index files.
 */
async function resolveImportPath(
  importPath: string,
  fromFile: string,
  rootDir: string,
): Promise<string | null> {
  const fromDir = dirname(join(rootDir, fromFile))
  const basePath = resolve(fromDir, importPath)
  const relBase = relative(rootDir, basePath).replace(/\\/g, '/')

  // Strip .js/.mjs extensions (TypeScript convention)
  const withoutExt = relBase.replace(/\.(js|mjs|cjs)$/, '')

  // Try exact match first, then with extensions, then as directory/index
  const candidates = [
    relBase,
    withoutExt + '.ts',
    withoutExt + '.tsx',
    withoutExt + '.js',
    withoutExt + '.jsx',
    withoutExt + '/index.ts',
    withoutExt + '/index.tsx',
    withoutExt + '/index.js',
  ]

  for (const candidate of candidates) {
    try {
      const fullPath = join(rootDir, candidate)
      const s = await stat(fullPath)
      if (s.isFile()) {
        return candidate
      }
    } catch {
      // File doesn't exist, try next candidate
    }
  }

  return null
}

/**
 * Scan a project directory and build the architecture graph.
 */
export async function scanArchitecture(
  rootDir: string,
): Promise<ArchitectureGraph> {
  const sourceFiles = await collectSourceFiles(rootDir, rootDir)
  const dependencies: Record<string, string[]> = {}
  const importedBy: Record<string, Set<string>> = {}

  // Scan each file for imports
  for (const filePath of sourceFiles) {
    const fullPath = join(rootDir, filePath)
    let content: string
    try {
      content = await readFile(fullPath, 'utf-8')
    } catch {
      continue
    }

    const rawImports = extractImports(content)
    const resolved: string[] = []

    for (const imp of rawImports) {
      const resolvedPath = await resolveImportPath(imp, filePath, rootDir)
      if (resolvedPath) {
        resolved.push(resolvedPath)
        // Track reverse mapping
        if (!importedBy[resolvedPath]) {
          importedBy[resolvedPath] = new Set()
        }
        importedBy[resolvedPath].add(filePath)
      }
    }

    dependencies[filePath] = resolved
  }

  // Identify entry points (files that nothing imports)
  const entryPoints = sourceFiles.filter(
    (f) => !importedBy[f] || importedBy[f].size === 0,
  )

  // Group files by directory (module grouping)
  const modules: Record<string, string[]> = {}
  for (const filePath of sourceFiles) {
    const dir = dirname(filePath)
    const moduleKey = dir === '.' ? 'root' : dir
    if (!modules[moduleKey]) {
      modules[moduleKey] = []
    }
    modules[moduleKey].push(filePath)
  }

  return {
    version: 1,
    dependencies,
    entryPoints,
    modules,
    builtAt: Date.now(),
    rootDir,
  }
}

/**
 * Generate a compact summary of the architecture for system prompt injection.
 * Stays under ~300 tokens.
 */
export function summarizeArchitecture(graph: ArchitectureGraph): string {
  const fileCount = Object.keys(graph.dependencies).length
  const moduleCount = Object.keys(graph.modules).length

  // Find the most-imported files (highest fan-in)
  const fanIn: Record<string, number> = {}
  for (const deps of Object.values(graph.dependencies)) {
    for (const dep of deps) {
      fanIn[dep] = (fanIn[dep] ?? 0) + 1
    }
  }
  const topImported = Object.entries(fanIn)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 5)
    .map(([path, count]) => `  ${path} (${count} importers)`)

  // Top modules by file count
  const topModules = Object.entries(graph.modules)
    .sort(([, a], [, b]) => b.length - a.length)
    .slice(0, 5)
    .map(([dir, files]) => `  ${dir}/ (${files.length} files)`)

  const lines = [
    `${fileCount} source files across ${moduleCount} modules.`,
    `Entry points: ${graph.entryPoints.slice(0, 3).join(', ')}${graph.entryPoints.length > 3 ? ` (+${graph.entryPoints.length - 3} more)` : ''}`,
  ]

  if (topModules.length > 0) {
    lines.push('Key modules:')
    lines.push(...topModules)
  }

  if (topImported.length > 0) {
    lines.push('Most-imported files:')
    lines.push(...topImported)
  }

  return lines.join('\n')
}
