/**
 * Layer 2: AI Execution Sink Detection
 * Detects patterns where LLM output is fed into dangerous execution sinks
 *
 * Covers B2: Unsafe execution of model output (LLM02)
 *
 * Sinks include:
 * - Code execution: eval(), Function(), vm.runInContext()
 * - Shell execution: exec(), spawn(), child_process
 * - SQL builders: .query(), .execute(), .raw()
 * - Template rendering: innerHTML, dangerouslySetInnerHTML
 */

import type { Vulnerability, VulnerabilitySeverity } from '../../shared/types'
import type { ParsedFile } from '../../shared/parsed-file'
import {
  isComment,
  isTestOrMockFile,
  isScannerOrFixtureFile,
  isExampleDirectory,
  isLibraryCode,
} from '../../parse/file-classifier'
import { isLLMContextFile } from './prompt-hygiene'

const BASE_CONFIDENCE = 0.55

// ============================================================================
// LLM Output Variable Detection
// ============================================================================

/**
 * Check if line contains LLM API response context
 */
function hasLLMResponseContext(lineContent: string, surroundingContext: string): boolean {
  const llmResponsePatterns = [
    /\.choices\[0\]\.message\.content/i,     // OpenAI response
    /\.content\[0\]\.text/i,                  // Anthropic response
    /completion\.text/i,                       // Generic completion
    /\.data\.choices/i,                        // API response
    /await\s+\w+\.(?:chat|messages|completions)\.create/i, // API call
    /response\.text\s*\(/i,                    // Response text method
  ]

  const fullContext = lineContent + '\n' + surroundingContext
  return llmResponsePatterns.some(p => p.test(fullContext))
}

// ============================================================================
// UI Suggestion / Template Pattern Detection (False Positive Filters)
// ============================================================================

/**
 * Check if this is a UI suggestion/template pattern rather than execution sink
 * These patterns create display strings for command palettes, autocomplete, etc.
 */
function isUITemplateSuggestion(lineContent: string, surroundingContext: string): boolean {
  const fullContext = lineContent + '\n' + surroundingContext

  // UI suggestion object patterns (command palette, autocomplete suggestions)
  // Note: Be careful not to match variable declarations like `const completion =`
  const uiSuggestionPatterns = [
    // Object property patterns for suggestion items (key: value in objects)
    /(?:id|key|label|title|name|description|display|text|value|placeholder):\s*`[^`]*\$\{/i,
    // Common suggestion UI patterns (arrays or objects, not variable declarations)
    /(?:set)?suggestions\s*[=:]\s*\[/i,  // suggestions: [...] or setSuggestions([])
    /autocomplete/i,
    /command\s*palette/i,
    /fuzzy\s*search/i,
    /search\s*result/i,
    // React/UI state patterns
    /useState.*suggestions|setSuggestions/i,
    /setItems|setResults/i,
    // Template ID generation for UI
    /id:\s*`[a-z]+-\$\{/i,  // id: `delete-${...}`, id: `edit-${...}`
  ]

  // These patterns should NOT be considered UI suggestions
  const notUISuggestionPatterns = [
    /\.query\s*\(/i,
    /\.execute\s*\(/i,
    /\.raw\s*\(/i,
    /await\s+db\./i,
    /prisma\./i,
    /supabase\./i,
    /knex\./i,
    /sequelize\./i,
    /child_process/i,
    /exec\s*\(/i,
    /spawn\s*\(/i,
    /eval\s*\(/i,
    /fetch\s*\(/i,
    /axios\./i,
    /\.redirect\s*\(/i,
    /\.setHeader\s*\(/i,
    /\.cookie\s*\(/i,
    /location\./i,
  ]

  // Check if context matches UI pattern but NOT execution pattern
  const matchesUIPattern = uiSuggestionPatterns.some(p => p.test(fullContext))
  const matchesExecutionPattern = notUISuggestionPatterns.some(p => p.test(lineContent))

  return matchesUIPattern && !matchesExecutionPattern
}

/**
 * Check if this is a static template string (no actual LLM output interpolation)
 * e.g., `delete ${node.title}` where node is app data, not LLM output
 */
function isAppDataInterpolation(lineContent: string, surroundingContext: string): boolean {
  const fullContext = lineContent + '\n' + surroundingContext

  // Patterns indicating the interpolated variable is app data, not LLM output
  const appDataPatterns = [
    // Database result/record properties
    /\$\{(?:result|item|record|row|entry|node)\.(?:id|title|name|slug|key|label)\}/i,
    // UI state properties
    /\$\{(?:selected|current|active|item|node)\.(?:id|title|name|value)\}/i,
    // Form/input data
    /\$\{(?:data|values|form|input)\.(?:id|name|value)\}/i,
    // Array iteration context
    /\.map\s*\(\s*\(?(?:item|node|row|entry|result)/i,
    /\.forEach\s*\(\s*\(?(?:item|node|row|entry|result)/i,
  ]

  // Patterns that suggest LLM output (should not skip)
  const llmOutputPatterns = [
    /\$\{(?:response|completion|generated|output|answer|reply|message)\.?/i,
    /\$\{(?:ai|llm|gpt|claude|chat)(?:Response|Output|Result)/i,
    /\.choices\[0\]/i,
    /\.content\[0\]\.text/i,
  ]

  const isAppData = appDataPatterns.some(p => p.test(fullContext))
  const isLLMOutput = llmOutputPatterns.some(p => p.test(fullContext))

  return isAppData && !isLLMOutput
}

// ============================================================================
// Sandbox and Validation Detection
// ============================================================================

/**
 * Check if execution is sandboxed
 */
function isSandboxedExecution(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 25)
  const contextEnd = Math.min(_lines.length, lineNumber + 10)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const sandboxPatterns = [
    /vm2/i,
    /isolated-vm/i,
    /safeeval/i,
    /safe-eval/i,
    /sandbox/i,
    /runInNewContext.*\{.*timeout/i,
    /runInContext.*\{.*timeout/i,
    /allowedGlobals/i,
    /allowedModules/i,
    /quickjs/i,
    /webworker/i,
    /iframe.*sandbox/i,
  ]

  return sandboxPatterns.some(p => p.test(context))
}

/**
 * Check if output has validation before execution
 */
function hasOutputValidation(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const validationPatterns = [
    /validate/i,
    /sanitize/i,
    /escape/i,
    /\.filter\s*\([^)]*(?:allowed|safe|valid)/i,  // .filter(x => allowed.includes(x))
    /parse.*catch/i,
    /schema\./i,
    /\.parse\s*\(/i,
    /allowlist/i,
    /whitelist/i,
    /blocklist/i,
    /blacklist/i,
    /allowed(?:Columns|Tables|Hosts|Domains|Extensions|Types|Args|Paths)/i,  // Allowlist variable names
    /JSON\.parse.*catch/i,
    /DOMPurify/i,
    /xss/i,
    /encodeURIComponent/i,
    /\.replace\s*\(\s*\/\[.*\]\/[gi]*/i,  // Regex sanitization like .replace(/[^a-z0-9]/gi, '')
    /textContent\s*=/i,  // Using textContent (safe) instead of innerHTML
    /ReactMarkdown/i,  // React Markdown sanitizes by default
    /ast\.literal_eval/i,  // Python safe eval
    /yaml\.(?:safe_load|SafeLoader)/i,  // Safe YAML parsing
    /\.startsWith\s*\(\s*['"]\/['"]?\)/i,  // Relative URL check
    /new\s+URL\s*\(.*\).*origin/i,  // URL origin check
    /path\.resolve.*startsWith/i,  // Path validation
  ]

  return validationPatterns.some(p => p.test(context))
}

/**
 * Check if this appears to be display-only usage (not execution)
 */
function isDisplayOnly(lineContent: string, surroundingContext: string): boolean {
  const displayPatterns = [
    /console\.(log|info|debug|warn)/i,
    /textContent\s*=/i,
    /innerText\s*=/i,
    /\.text\s*=/i,
    /setState.*display/i,
    /render.*\{/i,
    /<p>|<div>|<span>/i,
    /\.send\s*\(/i,
    /\.json\s*\(/i,
    /return\s+.*response/i,
  ]

  const fullContext = lineContent + '\n' + surroundingContext
  return displayPatterns.some(p => p.test(fullContext))
}

// ============================================================================
// Pattern Definitions
// ============================================================================

type SinkType = 'code_execution' | 'shell_command' | 'sql_builder' | 'template_render'

interface ExecutionSinkPattern {
  name: string
  pattern: RegExp
  sinkType: SinkType
  baseSeverity: VulnerabilitySeverity
  description: string
  suggestedFix: string
}

const EXECUTION_SINK_PATTERNS: ExecutionSinkPattern[] = [
  // ========== Code Execution Sinks ==========
  {
    name: 'LLM output to eval()',
    pattern: /eval\s*\(\s*(?:response|result|output|completion|message|content|answer|generated|text)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'LLM output is passed directly to eval(). This allows arbitrary code execution if the model is manipulated via prompt injection.',
    suggestedFix: 'Never eval() LLM output. Use structured output (JSON schema) and validate before processing. Consider using a sandboxed environment like vm2 if code execution is required.',
  },
  {
    name: 'LLM output to Function constructor',
    pattern: /new\s+Function\s*\([^)]*(?:response|result|output|completion|message|content|answer|generated)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'LLM output is passed to Function constructor, which is equivalent to eval().',
    suggestedFix: 'Use JSON schemas to define expected output structure. Validate output before any processing.',
  },
  {
    name: 'LLM output to vm.runInContext',
    pattern: /vm\.run(?:InContext|InNewContext|InThisContext)\s*\(\s*(?:response|result|output|completion|content)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'high',
    description: 'LLM output executed in Node.js VM context. While isolated, VM can still be escaped in some versions.',
    suggestedFix: 'Use vm2 or isolated-vm for proper sandboxing. Add timeout and memory limits. Validate output structure before execution.',
  },
  // Generic pattern for code from LLM
  {
    name: 'Dynamic code execution from AI',
    pattern: /(?:eval|exec|execute)\s*\(\s*(?:ai|llm|gpt|claude|chat)(?:Response|Output|Result|Code)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated code is being executed dynamically.',
    suggestedFix: 'Use a sandboxed code execution environment. Validate and restrict the allowed operations.',
  },

  // ========== Shell Command Sinks ==========
  {
    name: 'LLM output to exec()',
    pattern: /(?:exec|execSync)\s*\(\s*(?:response|result|output|completion|command|content)(?:\.|\.data\.|\.text)?/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'LLM output is passed to shell exec(). Attackers can execute arbitrary system commands via prompt injection.',
    suggestedFix: 'Never pass LLM output directly to shell. Use allowlists for permitted commands. Parse structured output and use execFile() with fixed command and arguments.',
  },
  {
    name: 'LLM output to spawn()',
    pattern: /spawn\s*\(\s*(?:response|result|output|completion|command|content)(?:\.|\.data\.|\.text)?/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'LLM output is passed to spawn(), allowing command execution.',
    suggestedFix: 'Use a predefined list of allowed commands. Parse LLM output to extract only arguments, not command names.',
  },
  {
    name: 'LLM output in shell template',
    pattern: /`[^`]*\$\{(?:response|result|output|completion|command|content)[^}]*\}[^`]*`\s*(?:,|\))\s*(?:exec|spawn|child_process)/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'LLM output is interpolated into a shell command template.',
    suggestedFix: 'Use execFile() with separate command and arguments array. Never interpolate AI output into shell strings.',
  },
  {
    name: 'child_process with AI output',
    pattern: /child_process\.\w+\s*\([^)]*(?:ai|llm|gpt|claude|chat)(?:Response|Output|Result)/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'AI-generated content passed to child_process module.',
    suggestedFix: 'Implement strict allowlisting of commands. Parse structured output from LLM.',
  },

  // ========== SQL Builder Sinks ==========
  {
    name: 'LLM output in raw SQL',
    pattern: /\.(?:query|execute|raw)\s*\(\s*(?:response|result|output|generated|sql|completion)(?:\.|\.data\.|\.text)?/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'critical',
    description: 'LLM-generated SQL is executed directly. This enables SQL injection via prompt manipulation.',
    suggestedFix: 'Use parameterized queries. Have LLM generate query parameters, not raw SQL. Validate generated SQL against an allowlist of patterns.',
  },
  {
    name: 'LLM output in SQL template',
    pattern: /`(?:SELECT|INSERT|UPDATE|DELETE)[^`]*\$\{(?:response|result|output|generated|completion)/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'critical',
    description: 'LLM output interpolated into SQL query template.',
    suggestedFix: 'Use parameterized queries. Have LLM output structured data (table names, conditions) that you validate against allowlists.',
  },
  {
    name: 'Dynamic SQL from AI',
    pattern: /(?:query|execute|sql)\s*\(\s*(?:ai|llm|gpt|claude)(?:Query|Sql|Response)/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'critical',
    description: 'AI-generated SQL query being executed.',
    suggestedFix: 'Validate SQL structure. Use read-only database connections. Implement query allowlisting.',
  },

  // ========== Template/DOM Sinks ==========
  {
    name: 'LLM output to innerHTML',
    pattern: /\.innerHTML\s*=\s*(?:response|result|output|completion|message|content|generated)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output assigned to innerHTML. If the model outputs malicious HTML/JS, it will execute (XSS).',
    suggestedFix: 'Use textContent for plain text. Sanitize HTML with DOMPurify before rendering. Use React/Vue which auto-escape by default.',
  },
  {
    name: 'LLM output to outerHTML',
    pattern: /\.outerHTML\s*=\s*(?:response|result|output|completion|message|content|generated)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output assigned to outerHTML. This replaces the entire element and allows XSS.',
    suggestedFix: 'Use textContent for plain text. Sanitize HTML with DOMPurify before rendering.',
  },
  {
    name: 'LLM output to insertAdjacentHTML',
    pattern: /\.insertAdjacentHTML\s*\([^,]+,\s*(?:response|result|output|completion|message|content|generated)/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to insertAdjacentHTML. This allows XSS via injected HTML/JS.',
    suggestedFix: 'Use insertAdjacentText for plain text. Sanitize HTML with DOMPurify: el.insertAdjacentHTML("beforeend", DOMPurify.sanitize(content))',
  },
  {
    name: 'LLM output to dangerouslySetInnerHTML',
    pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*(?:response|result|output|completion|message|content)/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output used in React dangerouslySetInnerHTML without sanitization.',
    suggestedFix: 'Sanitize with DOMPurify: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}',
  },
  {
    name: 'LLM output to document.write',
    pattern: /document\.write\s*\(\s*(?:response|result|output|completion|message|content)/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to document.write, allowing script injection.',
    suggestedFix: 'Use DOM manipulation methods with proper escaping. Never use document.write with dynamic content.',
  },

  // ========== M5: File System Sinks ==========
  {
    name: 'LLM output in file path',
    pattern: /(?:readFile|writeFile|readFileSync|writeFileSync|unlink|unlinkSync|mkdir|mkdirSync|rmdir|rmSync)\s*\(\s*(?:response|result|output|completion|message|content|path)(?:\.|\.data\.|\.path)?/gi,
    sinkType: 'code_execution', // Path traversal is code-level risk
    baseSeverity: 'critical',
    description: 'LLM-generated value used as file path. Path traversal attack possible - model could access or modify arbitrary files.',
    suggestedFix: 'Validate AI output against allowed paths: if (!allowedPaths.some(p => path.resolve(output).startsWith(p))) throw. Use path.resolve() and check the result is within allowed directory.',
  },
  {
    name: 'LLM output in fs operation',
    pattern: /fs\.(?:read|write|append|unlink|mkdir|rm|stat|access)\w*\s*\(\s*(?:response|result|output|completion|aiPath|generatedPath)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated path passed to filesystem operation. Model could traverse to sensitive directories.',
    suggestedFix: 'Create allowlist of permitted paths/directories. Use path.resolve() and validate result is within allowed boundaries.',
  },
  {
    name: 'LLM output in path.join',
    pattern: /path\.(?:join|resolve)\s*\([^)]*(?:response|result|output|completion|content|aiPath)[^)]*\).*(?:fs\.|readFile|writeFile)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'high',
    description: 'AI output used in path construction before file operation. Validate the final path.',
    suggestedFix: 'After path.join/resolve, check result is within allowed directory: const resolved = path.resolve(base, aiPath); if (!resolved.startsWith(allowedRoot)) throw',
  },

  // ========== M5: Dynamic Import Sinks ==========
  {
    name: 'LLM output in dynamic import',
    pattern: /import\s*\(\s*(?:response|result|output|completion|message|content|moduleName|aiModule)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated value used in dynamic import(). Arbitrary module loading enables code execution.',
    suggestedFix: 'Use allowlist for permitted modules: const allowed = ["lodash", "moment"]; if (!allowed.includes(moduleName)) throw. Never dynamically import AI-generated module paths.',
  },
  {
    name: 'LLM output in require()',
    pattern: /require\s*\(\s*(?:response|result|output|completion|message|content|moduleName|aiModule)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated value used in require(). Can load arbitrary modules including native code.',
    suggestedFix: 'Use allowlist for permitted modules. Consider using import maps or module aliases instead of dynamic require.',
  },
  {
    name: 'LLM output in module resolution',
    pattern: /(?:require\.resolve|import\.meta\.resolve)\s*\(\s*(?:response|result|output|completion|moduleName)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'high',
    description: 'AI output used in module path resolution. Could leak information about file system or enable module confusion attacks.',
    suggestedFix: 'Validate module name against allowlist before resolution.',
  },

  // ========== Phase 2: Network/SSRF Sinks ==========
  {
    name: 'LLM output in fetch URL',
    pattern: /fetch\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl|urlFromAi)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'code_execution', // SSRF is code-level risk
    baseSeverity: 'critical',
    description: 'AI-generated URL passed to fetch(). Attackers can manipulate the model to make requests to internal services (SSRF), exfiltrate data, or access localhost services.',
    suggestedFix: 'Validate URL against allowlist: const allowed = ["api.example.com"]; if (!allowed.includes(new URL(url).host)) throw. Block private IP ranges.',
  },
  {
    name: 'LLM output in axios request',
    pattern: /axios\.(?:get|post|put|delete|patch|request)\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated URL passed to axios. This enables SSRF attacks where the model is manipulated to make requests to internal services.',
    suggestedFix: 'Validate URL host against allowlist. Use axios interceptors to block private IPs and internal hosts.',
  },
  {
    name: 'LLM output in axios config',
    pattern: /axios\s*\(\s*\{[^}]*url:\s*(?:response|result|output|completion|aiUrl|generatedUrl)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated URL passed to axios via config object. SSRF risk.',
    suggestedFix: 'Validate URL host against allowlist before passing to axios.',
  },
  {
    name: 'LLM output in HTTP client',
    pattern: /(?:got|request|superagent|ky|undici\.fetch)\s*\(\s*(?:response|result|output|completion|aiUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated URL passed to HTTP client. Server-Side Request Forgery (SSRF) risk.',
    suggestedFix: 'Validate URLs against allowlist of permitted hosts. Block internal IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, localhost).',
  },

  // ========== Phase 2: Redirect Sinks ==========
  {
    name: 'LLM output in server redirect',
    pattern: /(?:res|response)\.redirect\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render', // Open redirect is similar to XSS
    baseSeverity: 'high',
    description: 'AI-generated URL used in HTTP redirect. Attackers can craft prompts to redirect users to phishing sites or malicious pages.',
    suggestedFix: 'Validate redirect URL against allowlist. Only allow redirects to same-origin or known safe domains. Use relative URLs where possible.',
  },
  {
    name: 'LLM output in client redirect assignment',
    pattern: /(?:window\.)?location\.href\s*=\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated URL assigned to location.href. Enables open redirect attacks.',
    suggestedFix: 'Validate URL before assignment. Prefer relative URLs or validate against allowlist: if (!url.startsWith("/") && !allowedHosts.includes(new URL(url).host)) throw',
  },
  {
    name: 'LLM output in location.assign',
    pattern: /location\.assign\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated URL passed to location.assign(). Enables open redirect attacks.',
    suggestedFix: 'Validate URL before assignment. Only allow same-origin or allowlisted domains.',
  },
  {
    name: 'LLM output in location.replace',
    pattern: /location\.replace\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated URL passed to location.replace(). Enables open redirect attacks.',
    suggestedFix: 'Validate URL before assignment. Only allow same-origin or allowlisted domains.',
  },
  {
    name: 'LLM output in Next.js redirect',
    pattern: /redirect\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated URL passed to Next.js redirect(). Enables open redirect attacks.',
    suggestedFix: 'Validate URL before redirect. Only allow relative URLs or allowlisted domains.',
  },
  {
    name: 'LLM output in meta refresh',
    pattern: /<meta[^>]*http-equiv\s*=\s*['"`]refresh['"`][^>]*content\s*=\s*['"`][^'"]*url\s*=\s*(?:\$\{|<%=).*(?:response|output|completion)/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated URL in meta refresh tag. Open redirect vulnerability.',
    suggestedFix: 'Avoid meta refresh with dynamic URLs. Use server-side redirects with URL validation instead.',
  },

  // ========== Phase 2: Header Injection Sinks ==========
  {
    name: 'LLM output in response header',
    pattern: /(?:res|response)\.(?:setHeader|set|header)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:response|result|output|completion|aiValue)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated value used in HTTP response header. Enables header injection attacks (CRLF injection, cache poisoning).',
    suggestedFix: 'Sanitize header values: remove CR/LF characters. Validate against expected format. Never use AI output directly in security-sensitive headers (Set-Cookie, Authorization).',
  },
  {
    name: 'LLM output in cookie',
    pattern: /(?:res|response)\.(?:cookie|setCookie)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated value set as cookie. Could enable session fixation or cookie injection attacks.',
    suggestedFix: 'Never use AI output for cookie values. Generate tokens server-side with crypto.randomBytes(). Validate any user-facing values.',
  },
  {
    name: 'LLM output in res.type',
    pattern: /(?:res|response)\.type\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'AI-generated value used to set Content-Type. Could enable MIME confusion attacks.',
    suggestedFix: 'Use allowlist for content types: const allowed = ["json", "html", "text"]; if (!allowed.includes(type)) throw',
  },

  // ========== Phase 3: Additional Code Execution Sinks ==========
  {
    name: 'LLM output to setTimeout/setInterval string',
    pattern: /(?:setTimeout|setInterval)\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'high',
    description: 'AI-generated string passed to setTimeout/setInterval. When passed a string, these functions act like eval().',
    suggestedFix: 'Never pass strings to setTimeout/setInterval. Use arrow functions: setTimeout(() => doSomething(), 1000)',
  },
  {
    name: 'LLM output to globalThis.eval',
    pattern: /(?:globalThis|window)\[?['"]?eval['"]?\]?\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated code passed to eval via globalThis/window. This is indirect eval() that enables arbitrary code execution.',
    suggestedFix: 'Never eval() LLM output. Use structured output and validation.',
  },
  {
    name: 'LLM output to execa',
    pattern: /execa\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'AI-generated command passed to execa. This enables command injection attacks.',
    suggestedFix: 'Never pass LLM output directly to shell. Use allowlists for permitted commands.',
  },

  // ========== Phase 3: Python-Specific Sinks ==========
  {
    name: 'LLM output to Python eval',
    pattern: /eval\s*\(\s*(?:response|result|output|completion|code)(?:\[['"]?choices['"]?\]\[0\]\[['"]?message['"]?\]\[['"]?content['"]?\]|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated code passed to Python eval(). Enables arbitrary code execution.',
    suggestedFix: 'Never eval() LLM output. Use ast.literal_eval() for safe literal evaluation, or JSON parsing with schema validation.',
  },
  {
    name: 'LLM output to Python exec',
    pattern: /exec\s*\(\s*(?:response|result|output|completion)(?:\[['"]?choices['"]?\]\[0\]\[['"]?message['"]?\]\[['"]?content['"]?\]|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated code passed to Python exec(). Enables arbitrary code execution.',
    suggestedFix: 'Never exec() LLM output. Use structured output and validation instead.',
  },
  {
    name: 'LLM output to pickle.loads',
    pattern: /pickle\.loads?\s*\(\s*(?:response|result|output|completion|serialized)(?:\.encode\(\)|\.content|\.text)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'critical',
    description: 'AI-generated data passed to pickle.loads(). Pickle deserialization can execute arbitrary code.',
    suggestedFix: 'Never unpickle untrusted data. Use JSON or other safe serialization formats.',
  },
  {
    name: 'LLM output to subprocess with shell=True',
    pattern: /subprocess\.(?:run|call|Popen)\s*\(\s*(?:response|result|output|completion|ai_command|generated_cmd)(?:\.content|\.text)?[^)]*shell\s*=\s*True/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'AI-generated command passed to subprocess with shell=True. Enables command injection.',
    suggestedFix: 'Never use shell=True with user/AI input. Use subprocess.run(["cmd", "arg1", "arg2"]) without shell.',
  },
  {
    name: 'LLM output to os.system',
    pattern: /os\.system\s*\(\s*(?:response|result|output|completion|generated_cmd|ai_command)(?:\.content|\.text)?/gi,
    sinkType: 'shell_command',
    baseSeverity: 'critical',
    description: 'AI-generated command passed to os.system(). Enables command injection.',
    suggestedFix: 'Use subprocess.run() with list arguments instead of os.system(). Never pass AI output to shell.',
  },
  {
    name: 'Python SQL f-string injection',
    pattern: /cursor\.execute\s*\(\s*f["'].*\{.*(?:response|result|output|completion)/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'critical',
    description: 'AI-generated value interpolated into SQL query via f-string. Enables SQL injection.',
    suggestedFix: 'Use parameterized queries: cursor.execute("SELECT * FROM users WHERE id = ?", [user_id])',
  },

  // ========== Sprint 6: Template Engine Injection Sinks ==========
  {
    name: 'LLM output to EJS template',
    pattern: /\bejs\.(?:render|renderFile|compile)\s*\([^)]*(?:response|result|output|completion|content|message|text|answer)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to EJS template engine. Server-side template injection (SSTI) can lead to remote code execution.',
    suggestedFix: 'Sanitize LLM output before passing to templates. Use autoescaping and never pass AI output as template source.',
  },
  {
    name: 'LLM output to Handlebars template',
    pattern: /\b(?:handlebars|hbs)\.(?:compile|render)\s*\([^)]*(?:response|result|output|completion|content)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to Handlebars template. If used with SafeString, SSTI is possible.',
    suggestedFix: 'Never pass LLM output to Handlebars SafeString. Use default escaping and sanitize AI output.',
  },
  {
    name: 'LLM output to Pug/Jade template',
    pattern: /\bpug\.(?:render|compile|renderFile)\s*\([^)]*(?:response|result|output|completion|content)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to Pug template engine. Unescaped interpolation (!{}) enables SSTI.',
    suggestedFix: 'Use escaped interpolation (#{}) and sanitize LLM output before rendering.',
  },
  {
    name: 'LLM output to Nunjucks template',
    pattern: /\bnunjucks\.(?:render|renderString)\s*\([^)]*(?:response|result|output|completion|content)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output passed to Nunjucks template. SSTI risk if autoescape is disabled.',
    suggestedFix: 'Enable autoescape and sanitize LLM output before rendering.',
  },
  {
    name: 'LLM output to Jinja2 template (Python)',
    pattern: /\b(?:jinja2\.)?Template\s*\([^)]*(?:response|result|output|completion|content)(?:\.|\.content|\.text)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'high',
    description: 'LLM output used as Jinja2 template source. SSTI can lead to RCE in Python.',
    suggestedFix: 'Never use LLM output as template source. Use it only as template variables with autoescaping enabled.',
  },
  {
    name: 'LLM output in Mustache template',
    pattern: /\bMustache\.render\s*\([^)]*(?:response|result|output|completion|content)(?:\.|\.data\.|\.text|\.content)?/gi,
    sinkType: 'template_render',
    baseSeverity: 'medium',
    description: 'LLM output passed to Mustache template. While Mustache auto-escapes HTML, triple braces {{{...}}} bypass this.',
    suggestedFix: 'Ensure LLM output is never used with triple braces. Validate template structure.',
  },

  // ========== Sprint 6: NoSQL Injection Sinks ==========
  {
    name: 'NoSQL injection via JSON.parse',
    pattern: /\.(?:find|findOne|findOneAndUpdate|updateOne|updateMany|deleteOne|deleteMany|aggregate)\s*\(\s*JSON\.parse\s*\(\s*(?:response|result|output|completion|content)(?:\.|\.text|\.content)?/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'high',
    description: 'LLM output parsed as MongoDB query via JSON.parse. NoSQL injection can bypass authentication or leak data.',
    suggestedFix: 'Use parameterized queries or validate/sanitize LLM output against a schema before using in queries.',
  },
  {
    name: 'MongoDB $where injection',
    pattern: /\$where\s*:\s*[^}]*(?:response|result|output|completion|content|message)(?:\.|\.text|\.content)?/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'critical',
    description: 'LLM output in MongoDB $where operator. $where executes JavaScript, enabling arbitrary code execution.',
    suggestedFix: 'Avoid $where operator entirely. Use standard MongoDB query operators instead.',
  },
  {
    name: 'Dynamic MongoDB query from LLM',
    pattern: /(?:db|collection|mongoose)\.(?:find|findOne|aggregate)\s*\(\s*(?:response|result|output|completion|aiQuery)(?:\.|\.query|\.filter)?/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'high',
    description: 'LLM output used directly as MongoDB query. Query operators could be injected.',
    suggestedFix: 'Validate query structure. Only allow specific operators. Use schema validation before query execution.',
  },

  // ========== Sprint 6: GraphQL Injection Sinks ==========
  {
    name: 'GraphQL query injection',
    pattern: /\b(?:gql|graphql)\s*`[^`]*\$\{[^}]*(?:response|result|output|completion|content|message)[^}]*\}/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'high',
    description: 'LLM output interpolated into GraphQL query string. Can lead to query manipulation or unauthorized data access.',
    suggestedFix: 'Use GraphQL variables instead of string interpolation for dynamic values.',
  },
  {
    name: 'GraphQL query from LLM output',
    pattern: /(?:apolloClient|urqlClient|client)\.query\s*\(\s*\{[^}]*query\s*:\s*(?:response|result|output|completion|aiQuery)(?:\.|\.query)?/gi,
    sinkType: 'sql_builder',
    baseSeverity: 'high',
    description: 'LLM output used as GraphQL query. Malicious queries could access unauthorized data or cause DoS.',
    suggestedFix: 'Use predefined queries with variables. Validate query structure and depth before execution.',
  },

  // ========== Sprint 6: ReDoS (Regular Expression DoS) Sinks ==========
  {
    name: 'Dynamic regex from LLM output',
    pattern: /new\s+RegExp\s*\(\s*(?:response|result|output|completion|content|message|answer)(?:\.|\.text|\.content|\.pattern)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'medium',
    description: 'LLM-generated regex pattern. Malicious patterns can cause catastrophic backtracking (ReDoS), hanging the server.',
    suggestedFix: 'Validate regex complexity before compilation. Use safe-regex library or set timeout for regex execution.',
  },
  {
    name: 'Python regex from LLM output',
    pattern: /re\.compile\s*\(\s*(?:response|result|output|completion|content|pattern)(?:\.|\.text|\.content)?/gi,
    sinkType: 'code_execution',
    baseSeverity: 'medium',
    description: 'LLM-generated regex compiled in Python. ReDoS attacks can cause denial of service.',
    suggestedFix: 'Use regex_timeout or validate pattern complexity before compilation.',
  },
  {
    name: 'Dynamic regex replacement',
    pattern: /\.replace\s*\(\s*new\s+RegExp\s*\(\s*(?:response|result|output|completion|content)/gi,
    sinkType: 'code_execution',
    baseSeverity: 'medium',
    description: 'LLM output used as regex pattern in replace operation. ReDoS risk.',
    suggestedFix: 'Use string replace or validate regex pattern complexity.',
  },
]

// ============================================================================
// Phase 2: URL/Network Validation Detection
// ============================================================================

/**
 * Check if URL validation is present (returns 'strong', 'weak', or 'none')
 * Strong validation = skip finding entirely
 * Weak validation = downgrade severity
 */
function getURLValidationLevel(content: string, lineNumber: number, lines?: string[]): 'strong' | 'weak' | 'none' {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  // Strong validation - skip entirely
  const strongValidationPatterns = [
    /allowedHosts\.includes\s*\(\s*(?:new\s+URL)?/i,  // Explicit allowlist check
    /safeDomains\.includes\s*\(/i,  // Safe domain allowlist
    /allowedDomains\.includes\s*\(/i,  // Allowed domain check
    /if\s*\(\s*allowedHosts/i,  // Conditional on allowlist
    /if\s*\(\s*safeDomains/i,  // Conditional on safe domains
    /url\.origin\s*===\s*(?:window\.)?(?:location\.)?origin/i,  // Same-origin check
    /\.origin\s*===\s*origin/i,  // Same-origin check
    /\.startsWith\s*\(\s*['"]\/['"]?\s*\)\s*&&\s*!\s*\w+\.startsWith\s*\(\s*['"]\/\//i,  // Relative URL with protocol-relative check
    /if\s*\(\s*\w+\.startsWith\s*\(\s*['"]\/['"]?\s*\)\s*&&\s*!/i,  // Relative URL validation
    /blockedHosts\.includes\s*\(/i,  // Block list check
    /privateIpPatterns\.some\s*\(/i,  // Private IP blocking
  ]

  if (strongValidationPatterns.some(p => p.test(context))) {
    return 'strong'
  }

  // Weak validation - downgrade severity
  const weakValidationPatterns = [
    /isValidUrl|validateUrl|isAllowedUrl/i,
    /new\s+URL\s*\(.*\).*(?:host|hostname|origin)/i,
    /allowedUrls|allowedHosts|allowedDomains|safeDomains/i,
    /url\.startsWith\s*\(\s*['"`](?:https?:\/\/|\/[^\/])/i,
    /sanitizeUrl|encodeURIComponent/i,
    /blockedHosts|blockedDomains|privateIp/i,
    /\.includes\s*\(\s*(?:new\s+URL\s*\()?.*\.host/i,
  ]

  if (weakValidationPatterns.some(p => p.test(context))) {
    return 'weak'
  }

  return 'none'
}

/**
 * Legacy function for backward compatibility
 */
function hasURLValidation(content: string, lineNumber: number): boolean {
  return getURLValidationLevel(content, lineNumber) !== 'none'
}

/**
 * Check if DOM content is sanitized (e.g., DOMPurify)
 */
function isDOMSanitized(lineContent: string, surroundingContext: string): boolean {
  const fullContext = lineContent + '\n' + surroundingContext

  const sanitizationPatterns = [
    /DOMPurify\.sanitize\s*\(/i,
    /sanitizeHtml\s*\(/i,
    /xss\s*\(/i,
    /escapeHtml\s*\(/i,
    /textContent\s*=/i,  // textContent is safe
    /innerText\s*=/i,    // innerText is safe
    /ReactMarkdown/i,    // ReactMarkdown sanitizes by default
    /<ReactMarkdown>/i,  // JSX ReactMarkdown
  ]

  return sanitizationPatterns.some(p => p.test(fullContext))
}

/**
 * Check if file path is properly validated
 */
function isPathValidated(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const pathValidationPatterns = [
    /path\.resolve\s*\([^)]*\).*startsWith/i,  // Resolved path + startsWith check
    /resolved\.startsWith\s*\(/i,  // Common pattern: resolved.startsWith(baseDir)
    /!.*startsWith.*throw/i,  // Validation with throw on failure
    /if\s*\(\s*!?\s*resolved\.startsWith/i,  // Conditional path check
    /allowedExtensions\.includes\s*\(/i,  // Extension allowlist
    /allowedPaths/i,  // Path allowlist
    /SAFE_BASE_DIR/i,  // Common safe directory constant
    /baseDir|safeDir|allowedDir/i,  // Directory restriction variables
    /path\.basename\s*\(/i,  // Only using basename (no traversal)
    /\.replace\s*\(/i,  // Generic replace (likely sanitization)
  ]

  return pathValidationPatterns.some(p => p.test(context))
}

/**
 * Check if header value is sanitized
 */
function isHeaderSanitized(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const headerSanitizationPatterns = [
    /\.replace\s*\(\s*\/\[\\r\\n\]/i,  // CRLF removal
    /\.replace\s*\(\s*\/\[\\\\r\\\\n\]/i,  // CRLF removal (escaped)
    /allowedTypes\.includes\s*\(/i,  // Content-type allowlist
    /allowed(?:Headers|Types|Values)\.includes\s*\(/i,  // Generic allowlist
    /if\s*\(\s*allowed\w*\.includes\s*\(/i,  // Conditional allowlist
    /crypto\.random/i,  // Server-generated value (not AI)
    /randomUUID/i,  // UUID generation
    /safeValue|sanitized/i,  // Variable indicating sanitization
    /\/\^?\[a-zA-Z0-9\-_\.\s\]\+\$?\/.*\.test\s*\(/i,  // Regex validation (alphanumeric chars only)
    /if\s*\(\/\^?\[a-zA-Z0-9/i,  // Conditional with alphanumeric regex
  ]

  return headerSanitizationPatterns.some(p => p.test(context))
}

/**
 * Check for Python-specific safe patterns
 */
function isPythonSafe(lineContent: string, surroundingContext: string): boolean {
  const fullContext = lineContent + '\n' + surroundingContext

  const pythonSafePatterns = [
    /ast\.literal_eval\s*\(/i,  // Safe literal evaluation
    /yaml\.(?:safe_load|SafeLoader)/i,  // Safe YAML
    /yaml\.load\s*\([^)]*Loader\s*=\s*yaml\.SafeLoader/i,  // Explicit SafeLoader
    /cursor\.execute\s*\([^,]+,\s*\[/i,  // Parameterized query with list
    /\?\s*,\s*\[/i,  // SQL placeholder with params
    /%s.*,\s*\[/i,  // Python %s placeholder with list
    /subprocess\.run\s*\(\s*\[/i,  // subprocess with list (no shell)
    /shell\s*=\s*False/i,  // Explicit shell=False
  ]

  return pythonSafePatterns.some(p => p.test(fullContext))
}

/**
 * Check if SQL is using parameterized queries or ORM
 */
function isSQLParameterized(lineContent: string, surroundingContext: string): boolean {
  const fullContext = lineContent + '\n' + surroundingContext

  const parameterizedPatterns = [
    /allowedColumns\.filter\s*\(/i,  // Column allowlist
    /safeColumns/i,  // Safe column variable
    /allowedColumns\.includes\s*\(/i,  // Column allowlist check
    /\.filter\s*\(\s*\w+\s*=>\s*allowed\w*\.includes/i,  // Filter with allowlist
    /schema\.parse\s*\(/i,  // Zod schema validation
    /z\.enum\s*\(\s*\[/i,  // Zod enum (allowlist)
    /prisma\.\w+\.(?:findMany|findUnique|create|update)/i,  // Prisma ORM methods (not raw)
    /\$\{.*\}.*WHERE.*=\s*\$\d/i,  // Dynamic column but parameterized value
  ]

  return parameterizedPatterns.some(p => p.test(fullContext))
}

/**
 * Check if shell execution uses allowlist
 */
function isShellAllowlisted(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const shellAllowlistPatterns = [
    /allowedArgs\.includes\s*\(/i,  // Argument allowlist
    /if\s*\(\s*allowedArgs\.includes/i,  // Conditional on allowlist
    /allowedCommands\.includes\s*\(/i,  // Command allowlist
    /execFile\s*\(\s*['"][^'"]+['"]/i,  // execFile with hardcoded command (safe)
    /\.replace\s*\(\s*\/\[^a-z0-9\]/gi,  // Strict sanitization
    /sanitized\s*=/i,  // Sanitization variable
  ]

  return shellAllowlistPatterns.some(p => p.test(context))
}

/**
 * Check if template engine has autoescape enabled or uses safe rendering
 */
function isTemplateSafe(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const templateSafePatterns = [
    /autoescape\s*[=:]\s*true/i,  // Autoescape enabled
    /autoescaping\s*[=:]\s*true/i,  // Alternative naming
    /escape\s*[=:]\s*true/i,  // Escape option
    /\.escapeHtml\s*\(/i,  // Manual escaping
    /sanitize(?:Html|Output)?\s*\(/i,  // Sanitization function
    /DOMPurify\.sanitize/i,  // DOMPurify sanitization
    /#{[^}]+}/i,  // Pug escaped interpolation (safe)
    /\{\{[^}]+\}\}/i,  // Handlebars/Mustache double-brace (escaped by default)
  ]

  // Patterns that indicate unsafe usage
  const unsafePatterns = [
    /autoescape\s*[=:]\s*false/i,  // Autoescape disabled
    /!{[^}]+}/i,  // Pug unescaped interpolation
    /{{{[^}]+}}}/i,  // Handlebars/Mustache triple-brace (unescaped)
    /SafeString/i,  // Handlebars SafeString (bypasses escaping)
    /\|safe\b/i,  // Jinja2/Nunjucks safe filter
  ]

  const isSafe = templateSafePatterns.some(p => p.test(context))
  const isUnsafe = unsafePatterns.some(p => p.test(context))

  return isSafe && !isUnsafe
}

/**
 * Check if NoSQL query uses schema validation or allowlist
 */
function isNoSQLSafe(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const safePatterns = [
    /schema\.parse\s*\(/i,  // Zod schema validation
    /\.validate\s*\(/i,  // Joi/Yup validation
    /allowedOperators/i,  // Operator allowlist
    /allowedFields/i,  // Field allowlist
    /sanitizeQuery/i,  // Query sanitization function
    /mongo-sanitize/i,  // mongo-sanitize library
    /mongoose\.(?:Schema|model)/i,  // Using Mongoose models (safer)
  ]

  return safePatterns.some(p => p.test(context))
}

/**
 * Check if regex has complexity validation or timeout
 */
function isRegexSafe(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const safePatterns = [
    /safe-regex/i,  // safe-regex library
    /recheck/i,  // recheck library
    /regex-timeout/i,  // Timeout wrapper
    /RE2/i,  // RE2 library (safe by design)
    /validateRegex/i,  // Custom validation
    /maxLength|maxPatternLength/i,  // Length limits
    /try\s*\{[^}]*new\s+RegExp[^}]*\}\s*catch/i,  // Try-catch around regex
  ]

  return safePatterns.some(p => p.test(context))
}

/**
 * Check if dynamic import uses allowlist
 */
function isImportAllowlisted(content: string, lineNumber: number, lines?: string[]): boolean {
  const _lines = lines ?? content.split('\n')
  const contextStart = Math.max(0, lineNumber - 15)
  const contextEnd = Math.min(_lines.length, lineNumber + 5)
  const context = _lines.slice(contextStart, contextEnd).join('\n')

  const importAllowlistPatterns = [
    /ALLOWED_PLUGINS\s*[=:]/i,  // Plugin allowlist
    /importMap\s*[=:]/i,  // Import map object
    /allowedModules/i,  // Module allowlist
    /if\s*\(\s*\w+\s+in\s+importMap\)/i,  // Key in import map
    /if\s*\(\s*loader\)/i,  // Loader function check (from allowlist)
    /\[aiModule\]\s*$/i,  // Array access into known object (allowlist lookup)
  ]

  return importAllowlistPatterns.some(p => p.test(context))
}

// ============================================================================
// Main Detection Function
// ============================================================================

/**
 * Get surrounding context for analysis
 */
function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 15, lines?: string[]): string {
  const _lines = lines ?? content.split('\n')
  const start = Math.max(0, lineIndex - windowSize)
  const end = Math.min(_lines.length, lineIndex + windowSize)
  return _lines.slice(start, end).join('\n')
}

/**
 * Calculate severity based on sandbox and validation status
 */
function calculateSeverity(
  baseSeverity: VulnerabilitySeverity,
  sinkType: SinkType,
  isSandboxed: boolean,
  hasValidation: boolean,
  isTestFile: boolean,
  isExample: boolean = false,
  isLibrary: boolean = false
): VulnerabilitySeverity {
  let severity = baseSeverity

  // Test files get significant downgrade
  if (isTestFile) {
    return 'info'
  }

  // Example/demo code - not production, for tutorials
  if (isExample) {
    return 'info'
  }

  // Library code - base utilities, consumers add restrictions
  if (isLibrary) {
    return 'info'
  }

  // Sandboxing provides major protection for code execution
  if (isSandboxed) {
    if (sinkType === 'code_execution') {
      severity = hasValidation ? 'low' : 'medium'
    } else {
      // Sandboxing less relevant for SQL/shell
      severity = hasValidation ? 'medium' : 'high'
    }
  } else if (hasValidation) {
    // Validation alone helps but doesn't eliminate risk
    if (baseSeverity === 'critical') {
      severity = 'high'
    } else if (baseSeverity === 'high') {
      severity = 'medium'
    }
  }

  return severity
}

/**
 * Main detection function for LLM output execution sinks
 */
export function detectAIExecutionSinks(
  content: string,
  filePath: string,
  options?: { parsed?: ParsedFile }
): Vulnerability[] {
  const vulnerabilities: Vulnerability[] = []

  // Skip non-applicable files
  if (isScannerOrFixtureFile(filePath)) return vulnerabilities

  // Only deeply scan files that appear to be in LLM context
  // But still do basic scanning on all files for obvious patterns
  const isLLMFile = isLLMContextFile(filePath, content)
  const lines = options?.parsed?.lines ?? content.split('\n')
  const isTestFile = isTestOrMockFile(filePath)
  const isExample = isExampleDirectory(filePath)
  const isLibrary = isLibraryCode(filePath)

  for (const pattern of EXECUTION_SINK_PATTERNS) {
    const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
    let match

    while ((match = regex.exec(content)) !== null) {
      const lineNumber = content.substring(0, match.index).split('\n').length
      const lineContent = lines[lineNumber - 1]?.trim() || ''

      // Skip comments
      if (isComment(lineContent)) continue

      const surroundingContext = getSurroundingContext(content, lineNumber - 1, 15, lines)

      // Check if this is actually in an LLM context
      const hasLLMContext = isLLMFile || hasLLMResponseContext(lineContent, surroundingContext)

      // ===== FALSE POSITIVE FILTERS =====

      // Skip UI suggestion/template patterns (command palettes, autocomplete, etc.)
      // These are display strings, not execution sinks
      if (isUITemplateSuggestion(lineContent, surroundingContext)) {
        continue
      }

      // Skip app data interpolation (e.g., ${node.title}, ${item.id})
      // where the interpolated data is from the app, not LLM output
      if (isAppDataInterpolation(lineContent, surroundingContext)) {
        continue
      }

      // For non-LLM files, require stronger signal
      if (!hasLLMContext) {
        // Check if the matched variable looks like LLM output
        const matchText = match[0]
        const variableMatch = matchText.match(/(?:response|result|output|completion|message|content|answer|generated|text)/i)
        if (!variableMatch) continue

        // Skip if this looks like display-only usage
        if (isDisplayOnly(lineContent, surroundingContext)) continue
      }

      // Check for sandboxing and validation
      const isSandboxed = isSandboxedExecution(content, lineNumber, lines)
      const hasValidation = hasOutputValidation(content, lineNumber, lines)

      // ===== SINK-SPECIFIC VALIDATION CHECKS =====

      // Phase 2: Check for URL validation on network/redirect sinks (SSRF, Open Redirect)
      const isNetworkSink = pattern.name.includes('fetch') || pattern.name.includes('axios') ||
        pattern.name.includes('HTTP') || pattern.name.includes('redirect') ||
        pattern.name.includes('location') || pattern.name.includes('got')
      if (isNetworkSink) {
        const urlValidLevel = getURLValidationLevel(content, lineNumber, lines)
        if (urlValidLevel === 'strong') {
          continue  // Skip - strong URL validation present
        }
      }

      // Phase 3: Check for DOM sanitization on template_render sinks
      const hasDOMSanitization = pattern.sinkType === 'template_render'
        ? isDOMSanitized(lineContent, surroundingContext)
        : false

      // Skip DOM findings if sanitized
      if (hasDOMSanitization && pattern.sinkType === 'template_render') {
        continue
      }

      // Check for header sanitization
      const isHeaderSink = pattern.name.includes('header') || pattern.name.includes('cookie') ||
        pattern.name.includes('res.type')
      if (isHeaderSink && isHeaderSanitized(content, lineNumber, lines)) {
        continue  // Skip - header value is sanitized
      }

      // Check for path validation on file system sinks
      const isFileSink = pattern.name.includes('file path') || pattern.name.includes('fs operation') ||
        pattern.name.includes('path.join')
      if (isFileSink && isPathValidated(content, lineNumber, lines)) {
        continue  // Skip - path is validated
      }

      // Check for SQL parameterization
      const isSQLSink = pattern.sinkType === 'sql_builder'
      if (isSQLSink && isSQLParameterized(lineContent, surroundingContext)) {
        continue  // Skip - SQL is parameterized or uses allowlist
      }

      // Check for shell allowlist
      const isShellSink = pattern.sinkType === 'shell_command'
      if (isShellSink && isShellAllowlisted(content, lineNumber, lines)) {
        continue  // Skip - shell command uses allowlist
      }

      // Check for import allowlist
      const isImportSink = pattern.name.includes('import') || pattern.name.includes('require')
      if (isImportSink && isImportAllowlisted(content, lineNumber, lines)) {
        continue  // Skip - import uses allowlist
      }

      // Check for template engine safety (autoescape, sanitization)
      const isTemplateEngineSink = pattern.name.includes('EJS') || pattern.name.includes('Handlebars') ||
        pattern.name.includes('Pug') || pattern.name.includes('Nunjucks') || pattern.name.includes('Jinja2') ||
        pattern.name.includes('Mustache')
      if (isTemplateEngineSink && isTemplateSafe(content, lineNumber, lines)) {
        continue  // Skip - template engine has safe configuration
      }

      // Check for NoSQL query safety
      const isNoSQLSink = pattern.name.includes('NoSQL') || pattern.name.includes('MongoDB') ||
        pattern.name.includes('$where')
      if (isNoSQLSink && isNoSQLSafe(content, lineNumber, lines)) {
        continue  // Skip - NoSQL query is validated
      }

      // Check for regex safety (ReDoS protection)
      const isRegexSink = pattern.name.includes('regex') || pattern.name.includes('RegExp')
      if (isRegexSink && isRegexSafe(content, lineNumber, lines)) {
        continue  // Skip - regex has safety measures
      }

      // Check for Python-specific safe patterns
      const isPythonSink = pattern.name.includes('Python') || pattern.name.includes('pickle') ||
        pattern.name.includes('subprocess') || pattern.name.includes('os.system')
      if (isPythonSink && isPythonSafe(lineContent, surroundingContext)) {
        continue  // Skip - Python code uses safe patterns
      }

      // Check for ast.literal_eval (Python safe eval) - this is a safe alternative to eval()
      // It matches the eval pattern because literal_eval contains "eval("
      if (pattern.name.includes('eval') && /ast\.literal_eval\s*\(/i.test(lineContent)) {
        continue  // Skip - ast.literal_eval is safe, only evaluates literals
      }

      // Check URL validation level for severity adjustment
      const hasURLValid = isNetworkSink ? getURLValidationLevel(content, lineNumber, lines) !== 'none' : false

      // Combine validation checks (URL validation counts as validation for network sinks)
      const effectiveValidation = hasValidation || hasURLValid

      // Calculate final severity
      const severity = calculateSeverity(
        pattern.baseSeverity,
        pattern.sinkType,
        isSandboxed,
        effectiveValidation,
        isTestFile,
        isExample,
        isLibrary
      )

      // Build description with context
      let description = pattern.description
      if (isSandboxed) {
        description += ' (Sandbox detected - risk somewhat mitigated.)'
      }
      if (hasValidation) {
        description += ' (Some validation detected nearby.)'
      }
      if (hasURLValid && !hasValidation) {
        description += ' (URL validation detected nearby.)'
      }
      if (isTestFile) {
        description += ' (In test file.)'
      } else if (isExample) {
        description += ' (In example/demo directory - tutorial code.)'
      } else if (isLibrary) {
        description += ' (Library code - consumers add restrictions.)'
      }

      // Skip info-level in non-LLM files to reduce noise
      if (severity === 'info' && !isLLMFile) continue

      vulnerabilities.push({
        id: `ai-exec-${filePath}-${lineNumber}-${pattern.sinkType}`,
        filePath,
        lineNumber,
        lineContent,
        severity,
        category: 'ai_unsafe_execution',
        title: pattern.name,
        description,
        suggestedFix: pattern.suggestedFix,
        confidence: hasLLMContext ? 'high' : 'medium',
        layer: 2,
        source: 'ai_code' as const,
        requiresAIValidation: severity !== 'info' && severity !== 'low',
        baseConfidence: BASE_CONFIDENCE,
      })
    }
  }

  return vulnerabilities
}
