import type { AnalysisResult } from '../types'; export function htmlReport(result: AnalysisResult, rootDir: string): string { const rel = (f: string) => f.replace(rootDir, '').replace(/\\/g, '/').replace(/^\//, ''); const langCards = result.detectedLanguages.map(l => { const pct = l.definitionCount > 0 ? Math.round((l.deadCount / l.definitionCount) * 100) : 0; return `
${cap(l.language)}
${l.fileCount} file${l.fileCount === 1 ? '' : 's'}
${l.deadCount} dead (${pct}%)
`; }).join(''); const kindBars = Object.entries(result.cleanup.byKind) .sort((a, b) => b[1] - a[1]) .map(([kind, count]) => { const max = Math.max(...Object.values(result.cleanup.byKind)); const pct = Math.round((count / max) * 100); return `
${cap(kind)}s
${count}
`; }).join(''); const rows = result.deadSymbols .map(({ definition, reason }) => ` ${escapeHtml(rel(definition.file))} ${definition.line} ${definition.kind} ${escapeHtml(definition.name)} ${definition.exported ? '' : ''} ${escapeHtml(reason)} ` ).join('\n'); return ` Dead Code Hunter Report

Dead Code Hunter

Generated ${new Date().toISOString().replace('T', ' ').slice(0, 19)} UTC
${result.scannedFiles}
Files Scanned
${result.deadSymbols.length}
Dead Symbols
~${result.cleanup.estimatedLines}
Est. Dead Lines
${result.durationMs}ms
Analysis Time
${result.detectedLanguages.length > 0 ? `

Languages Detected

${langCards}
` : ''} ${result.deadSymbols.length > 0 ? `

Cleanup Potential

~${result.cleanup.estimatedLines} lines removable across ${result.byFile.size} files
${kindBars}

Dead Symbols

${rows}
FileLineKindNameExportedReason
` : '

No dead code found!

'} `; } function escapeHtml(s: string): string { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function cap(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); }