/**
 * Hotspot Detector — identifies frequently-changed and highly-imported files.
 */

import { exec } from 'child_process'
import { promisify } from 'util'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import type { ArchitectureGraph, HotspotMap } from './types.js'

const execAsync = promisify(exec)

/**
 * Build a hotspot map from git history and architecture graph.
 */
export async function detectHotspots(
  architecture: ArchitectureGraph | null,
): Promise<HotspotMap> {
  const cwd = getCwd()

  // Get change frequency from git log
  const byChangeFrequency = await getChangeFrequency(cwd)

  // Compute import fan-in from architecture graph
  const byImportFanIn = computeImportFanIn(architecture)

  // Find test file mappings
  const testedFiles = findTestMappings(architecture)

  logForDebugging(
    `Hotspots: ${byChangeFrequency.length} files by frequency, ` +
      `${byImportFanIn.length} by fan-in`,
  )

  return {
    version: 1,
    byChangeFrequency,
    byImportFanIn,
    testedFiles,
    computedAt: Date.now(),
  }
}

async function getChangeFrequency(
  cwd: string,
): Promise<Array<{ path: string; changes: number }>> {
  try {
    const { stdout } = await execAsync(
      'git log --pretty=format: --name-only --diff-filter=ACMR -n 200 | sort | uniq -c | sort -rn | head -20',
      { cwd, timeout: 5000 },
    )

    return stdout
      .split('\n')
      .map((line) => line.trim())
      .filter((line) => line.length > 0)
      .map((line) => {
        const match = line.match(/^\s*(\d+)\s+(.+)$/)
        if (!match) return null
        return { path: match[2]!, changes: parseInt(match[1]!, 10) }
      })
      .filter((entry): entry is { path: string; changes: number } => entry !== null)
  } catch {
    return []
  }
}

function computeImportFanIn(
  architecture: ArchitectureGraph | null,
): Array<{ path: string; importers: number }> {
  if (!architecture) return []

  const fanIn: Record<string, number> = {}
  for (const deps of Object.values(architecture.dependencies)) {
    for (const dep of deps) {
      fanIn[dep] = (fanIn[dep] ?? 0) + 1
    }
  }

  return Object.entries(fanIn)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 20)
    .map(([path, importers]) => ({ path, importers }))
}

function findTestMappings(
  architecture: ArchitectureGraph | null,
): Array<{ source: string; test: string }> {
  if (!architecture) return []

  const allFiles = new Set(Object.keys(architecture.dependencies))
  const mappings: Array<{ source: string; test: string }> = []

  for (const filePath of allFiles) {
    // Skip if this is already a test file
    if (isTestFile(filePath)) continue

    // Look for corresponding test file
    const testCandidates = generateTestCandidates(filePath)
    for (const candidate of testCandidates) {
      if (allFiles.has(candidate)) {
        mappings.push({ source: filePath, test: candidate })
        break
      }
    }
  }

  return mappings
}

function isTestFile(path: string): boolean {
  return /\.(test|spec|e2e)\.(ts|tsx|js|jsx)$/.test(path) ||
    path.includes('__tests__/')
}

function generateTestCandidates(filePath: string): string[] {
  const base = filePath.replace(/\.(ts|tsx|js|jsx)$/, '')
  const ext = filePath.match(/\.(ts|tsx|js|jsx)$/)?.[0] ?? '.ts'

  return [
    `${base}.test${ext}`,
    `${base}.spec${ext}`,
    filePath.replace(/^(.+)\/([^/]+)$/, '$1/__tests__/$2'),
  ]
}
