/**
 * Import Resolver — Shared module for parsing imports/exports and resolving paths.
 *
 * Extracted from imported-auth-detector.ts and middleware-detector.ts to provide
 * a single source of truth for import resolution used by the module graph,
 * cross-file taint analysis, and existing auth detection.
 */

import type { ScanFile } from '../shared/types'

// ============================================================================
// Types
// ============================================================================

export interface ParsedImport {
  importPath: string        // raw path: '@/lib/db', './utils', 'express'
  importedNames: string[]   // ['query', 'findAll'] or ['default']
  isReExport: boolean       // export { x } from './y'
  isNamespace: boolean      // import * as x
  line: number              // 1-based
  isLocal: boolean          // starts with '.', '@/', '~/' (not node_modules)
}

export interface ParsedExport {
  name: string
  kind: 'function' | 'class' | 'variable' | 'reexport'
  line: number
  sourceModule?: string     // for re-exports: the source path
  isDefault: boolean
}

// ============================================================================
// File Index
// ============================================================================

/**
 * Build an O(1) lookup map for resolving import paths to ScanFiles.
 * Keys include the raw path and normalized variants for fast lookup.
 */
export function buildFileIndex(files: ScanFile[]): Map<string, ScanFile> {
  const index = new Map<string, ScanFile>()
  for (const file of files) {
    // Index by exact path
    index.set(file.path, file)

    // Index without extension for extension-less imports
    const withoutExt = file.path.replace(/\.(ts|tsx|js|jsx|mjs|cjs|py)$/, '')
    if (withoutExt !== file.path) {
      index.set(withoutExt, file)
    }

    // Index by path with @/ and ~/ alias (if it starts with src/)
    if (file.path.startsWith('src/')) {
      const aliasPath = file.path.slice(4)
      index.set('@/' + aliasPath, file)
      index.set('~/' + aliasPath, file)
      // Also index alias without extension
      if (withoutExt !== file.path) {
        const aliasNoExt = withoutExt.slice(4)
        index.set('@/' + aliasNoExt, file)
        index.set('~/' + aliasNoExt, file)
      }
    }

    // Index directory/index pattern — if path ends with /index.X, index by dir
    const indexMatch = file.path.match(/^(.+)\/index\.(ts|tsx|js|jsx|mjs|cjs)$/)
    if (indexMatch) {
      index.set(indexMatch[1], file)
    }
  }
  return index
}

// ============================================================================
// Import Extraction
// ============================================================================

/**
 * Extract all imports from a file's content.
 * Supports ES6 named/default/namespace, CommonJS destructured/default,
 * re-exports, and Python imports.
 */
export function extractAllImports(content: string, _filePath: string): ParsedImport[] {
  const imports: ParsedImport[] = []
  const lines = content.split('\n')
  const seen = new Set<string>()

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    const lineNumber = i + 1
    const trimmed = line.trim()

    // Skip comments
    if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*') || trimmed.startsWith('#')) {
      continue
    }

    let match: RegExpExecArray | null

    // --- ES6 re-exports: export { x, y } from './path' ---
    const reExportPattern = /export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g
    while ((match = reExportPattern.exec(line)) !== null) {
      const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n.length > 0)
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:reexport`
      if (seen.has(key)) continue
      seen.add(key)
      imports.push({
        importPath,
        importedNames: names,
        isReExport: true,
        isNamespace: false,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- ES6 namespace import: import * as x from 'path' ---
    const namespacePattern = /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g
    while ((match = namespacePattern.exec(line)) !== null) {
      const name = match[1]
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:namespace`
      if (seen.has(key)) continue
      seen.add(key)
      imports.push({
        importPath,
        importedNames: [name],
        isReExport: false,
        isNamespace: true,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- ES6 named imports: import { x, y } from 'path' ---
    const es6NamedPattern = /import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g
    while ((match = es6NamedPattern.exec(line)) !== null) {
      const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n.length > 0)
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:named`
      if (seen.has(key)) continue
      seen.add(key)
      imports.push({
        importPath,
        importedNames: names,
        isReExport: false,
        isNamespace: false,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- ES6 default import: import x from 'path' ---
    // Must not match 'import { ... }' or 'import * as ...'
    const defaultPattern = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g
    while ((match = defaultPattern.exec(line)) !== null) {
      const name = match[1]
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:default`
      if (seen.has(key)) continue
      seen.add(key)
      // Skip if already captured as named/namespace
      if (imports.some(imp => imp.line === lineNumber && imp.importPath === importPath)) continue
      imports.push({
        importPath,
        importedNames: ['default'],
        isReExport: false,
        isNamespace: false,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- CommonJS destructured: const { x } = require('path') ---
    const requireDestructurePattern = /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
    while ((match = requireDestructurePattern.exec(line)) !== null) {
      const names = match[1].split(',').map(n => n.trim().split(/\s*:\s*/)[0].trim()).filter(n => n.length > 0)
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:cjs-destructure`
      if (seen.has(key)) continue
      seen.add(key)
      imports.push({
        importPath,
        importedNames: names,
        isReExport: false,
        isNamespace: false,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- CommonJS default: const x = require('path') ---
    const requireDefaultPattern = /(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
    while ((match = requireDefaultPattern.exec(line)) !== null) {
      const name = match[1]
      const importPath = match[2]
      const key = `${lineNumber}:${importPath}:cjs-default`
      if (seen.has(key)) continue
      seen.add(key)
      // Skip if already captured as destructured
      if (imports.some(imp => imp.line === lineNumber && imp.importPath === importPath)) continue
      imports.push({
        importPath,
        importedNames: ['default'],
        isReExport: false,
        isNamespace: false,
        line: lineNumber,
        isLocal: isLocalImport(importPath),
      })
    }

    // --- Python: from x import y, z ---
    const pythonFromPattern = /^from\s+([\w.]+)\s+import\s+(.+)/
    const pythonFromMatch = trimmed.match(pythonFromPattern)
    if (pythonFromMatch) {
      const modulePath = pythonFromMatch[1]
      const names = pythonFromMatch[2].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(n => n.length > 0)
      const key = `${lineNumber}:${modulePath}:python-from`
      if (!seen.has(key)) {
        seen.add(key)
        imports.push({
          importPath: modulePath,
          importedNames: names,
          isReExport: false,
          isNamespace: false,
          line: lineNumber,
          isLocal: modulePath.startsWith('.'),
        })
      }
    }

    // --- Python: import x ---
    const pythonImportPattern = /^import\s+([\w.]+)(?:\s+as\s+\w+)?$/
    const pythonImportMatch = trimmed.match(pythonImportPattern)
    if (pythonImportMatch && !trimmed.includes('from')) {
      const modulePath = pythonImportMatch[1]
      const key = `${lineNumber}:${modulePath}:python-import`
      if (!seen.has(key)) {
        seen.add(key)
        imports.push({
          importPath: modulePath,
          importedNames: ['default'],
          isReExport: false,
          isNamespace: true,
          line: lineNumber,
          isLocal: modulePath.startsWith('.'),
        })
      }
    }
  }

  return imports
}

// ============================================================================
// Export Extraction
// ============================================================================

/**
 * Extract exported symbols from a file's content.
 * Supports ES6 exports, CommonJS module.exports, and Python top-level defs.
 */
export function extractExports(content: string, filePath: string): ParsedExport[] {
  const exports: ParsedExport[] = []
  const lines = content.split('\n')
  const isPython = filePath.endsWith('.py')

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    const lineNumber = i + 1
    const trimmed = line.trim()

    if (isPython) {
      // Python: top-level def and class
      const defMatch = trimmed.match(/^def\s+(\w+)\s*\(/)
      if (defMatch && !line.startsWith(' ') && !line.startsWith('\t')) {
        exports.push({ name: defMatch[1], kind: 'function', line: lineNumber, isDefault: false })
      }
      const classMatch = trimmed.match(/^class\s+(\w+)/)
      if (classMatch && !line.startsWith(' ') && !line.startsWith('\t')) {
        exports.push({ name: classMatch[1], kind: 'class', line: lineNumber, isDefault: false })
      }
      continue
    }

    // Skip comments
    if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue

    // --- Re-exports: export { x, y } from './path' ---
    const reExportMatch = trimmed.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/)
    if (reExportMatch) {
      const names = reExportMatch[1].split(',').map(n => {
        const parts = n.trim().split(/\s+as\s+/)
        return parts.length > 1 ? parts[1].trim() : parts[0].trim()
      }).filter(n => n.length > 0)
      const sourcePath = reExportMatch[2]
      for (const name of names) {
        exports.push({ name, kind: 'reexport', line: lineNumber, sourceModule: sourcePath, isDefault: name === 'default' })
      }
      continue
    }

    // --- export default function/class ---
    const exportDefaultFuncMatch = trimmed.match(/^export\s+default\s+(?:async\s+)?function\s+(\w+)/)
    if (exportDefaultFuncMatch) {
      exports.push({ name: exportDefaultFuncMatch[1], kind: 'function', line: lineNumber, isDefault: true })
      continue
    }
    const exportDefaultClassMatch = trimmed.match(/^export\s+default\s+class\s+(\w+)/)
    if (exportDefaultClassMatch) {
      exports.push({ name: exportDefaultClassMatch[1], kind: 'class', line: lineNumber, isDefault: true })
      continue
    }
    // export default <expression> (anonymous)
    if (/^export\s+default\s+/.test(trimmed) && !trimmed.includes('function') && !trimmed.includes('class')) {
      exports.push({ name: 'default', kind: 'variable', line: lineNumber, isDefault: true })
      continue
    }

    // --- Named exports: export function/const/class/let/var ---
    const exportFuncMatch = trimmed.match(/^export\s+(?:async\s+)?function\s+(\w+)/)
    if (exportFuncMatch) {
      exports.push({ name: exportFuncMatch[1], kind: 'function', line: lineNumber, isDefault: false })
      continue
    }
    const exportClassMatch = trimmed.match(/^export\s+class\s+(\w+)/)
    if (exportClassMatch) {
      exports.push({ name: exportClassMatch[1], kind: 'class', line: lineNumber, isDefault: false })
      continue
    }
    const exportVarMatch = trimmed.match(/^export\s+(?:const|let|var)\s+(\w+)/)
    if (exportVarMatch) {
      exports.push({ name: exportVarMatch[1], kind: 'variable', line: lineNumber, isDefault: false })
      continue
    }

    // --- export { x, y } (without from — local re-export) ---
    const exportListMatch = trimmed.match(/^export\s+\{([^}]+)\}(?!\s+from)/)
    if (exportListMatch) {
      const names = exportListMatch[1].split(',').map(n => {
        const parts = n.trim().split(/\s+as\s+/)
        return parts.length > 1 ? parts[1].trim() : parts[0].trim()
      }).filter(n => n.length > 0)
      for (const name of names) {
        exports.push({ name, kind: 'variable', line: lineNumber, isDefault: name === 'default' })
      }
      continue
    }

    // --- CommonJS: module.exports = { x, y } or module.exports.x = ---
    const moduleExportsObjMatch = trimmed.match(/^module\.exports\s*=\s*\{([^}]+)\}/)
    if (moduleExportsObjMatch) {
      const names = moduleExportsObjMatch[1].split(',').map(n => n.trim().split(/\s*:\s*/)[0].trim()).filter(n => n.length > 0)
      for (const name of names) {
        exports.push({ name, kind: 'variable', line: lineNumber, isDefault: false })
      }
      continue
    }
    const moduleExportsPropMatch = trimmed.match(/^module\.exports\.(\w+)\s*=/)
    if (moduleExportsPropMatch) {
      exports.push({ name: moduleExportsPropMatch[1], kind: 'variable', line: lineNumber, isDefault: false })
      continue
    }
    // module.exports = function/class
    if (/^module\.exports\s*=/.test(trimmed)) {
      exports.push({ name: 'default', kind: 'variable', line: lineNumber, isDefault: true })
      continue
    }

    // --- CommonJS: exports.x = ---
    const exportsMatch = trimmed.match(/^exports\.(\w+)\s*=/)
    if (exportsMatch) {
      exports.push({ name: exportsMatch[1], kind: 'variable', line: lineNumber, isDefault: false })
      continue
    }
  }

  return exports
}

// ============================================================================
// Path Resolution
// ============================================================================

/**
 * Resolve an import path to a ScanFile using the file index.
 * Handles @/, ~/, relative paths, extension resolution, and index file fallback.
 */
export function resolveImportPath(
  importPath: string,
  fromFilePath: string,
  fileIndex: Map<string, ScanFile>
): ScanFile | null {
  // Node modules — don't resolve
  if (!isLocalImport(importPath)) return null

  // Normalize alias paths
  let searchPath = importPath
  if (searchPath.startsWith('@/')) {
    searchPath = 'src/' + searchPath.slice(2)
  } else if (searchPath.startsWith('~/')) {
    searchPath = 'src/' + searchPath.slice(2)
  } else if (searchPath.startsWith('.')) {
    const fromDir = fromFilePath.substring(0, fromFilePath.lastIndexOf('/'))
    searchPath = resolvePath(fromDir, searchPath)
  }

  // Try exact match first (may have been indexed without extension)
  const exact = fileIndex.get(searchPath)
  if (exact) return exact

  // Try with common extensions
  const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py']
  for (const ext of extensions) {
    const candidate = fileIndex.get(searchPath + ext)
    if (candidate) return candidate
  }

  // Try index file fallback
  for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {
    const candidate = fileIndex.get(searchPath + '/index' + ext)
    if (candidate) return candidate
  }

  // Fallback: linear scan for suffix match (handles paths starting with or without prefix dirs)
  for (const [path, file] of fileIndex) {
    if (path.endsWith('/' + searchPath) || path === searchPath) return file
    for (const ext of extensions) {
      if (path.endsWith('/' + searchPath + ext) || path === searchPath + ext) return file
    }
  }

  return null
}

// ============================================================================
// Helpers
// ============================================================================

function isLocalImport(importPath: string): boolean {
  return importPath.startsWith('.') || importPath.startsWith('@/') || importPath.startsWith('~/')
}

function resolvePath(fromDir: string, relativePath: string): string {
  const parts = fromDir.split('/').filter(p => p.length > 0)
  const relParts = relativePath.split('/')
  for (const part of relParts) {
    if (part === '..') parts.pop()
    else if (part !== '.') parts.push(part)
  }
  return parts.join('/')
}
