/**
 * VS Code Diagnostic Formatter
 * Formats scan results as LSP diagnostic format for VS Code integration
 */

import type { Vulnerability, VulnerabilitySeverity } from '../../shared/types'

/**
 * LSP Diagnostic Severity
 * https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity
 */
export enum DiagnosticSeverity {
  Error = 1,
  Warning = 2,
  Information = 3,
  Hint = 4,
}

/**
 * LSP Position (0-indexed)
 */
export interface Position {
  line: number
  character: number
}

/**
 * LSP Range
 */
export interface Range {
  start: Position
  end: Position
}

/**
 * LSP Diagnostic
 * https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic
 */
export interface Diagnostic {
  range: Range
  severity: DiagnosticSeverity
  code?: string | number
  source: string
  message: string
  relatedInformation?: DiagnosticRelatedInformation[]
  tags?: DiagnosticTag[]
  data?: unknown
}

/**
 * LSP Diagnostic Related Information
 */
export interface DiagnosticRelatedInformation {
  location: {
    uri: string
    range: Range
  }
  message: string
}

/**
 * LSP Diagnostic Tag
 */
export enum DiagnosticTag {
  Unnecessary = 1,
  Deprecated = 2,
}

/**
 * Map vulnerability severity to LSP diagnostic severity
 */
function mapSeverity(severity: VulnerabilitySeverity): DiagnosticSeverity {
  switch (severity) {
    case 'critical':
    case 'high':
      return DiagnosticSeverity.Error
    case 'medium':
      return DiagnosticSeverity.Warning
    case 'low':
      return DiagnosticSeverity.Information
    case 'info':
      return DiagnosticSeverity.Hint
    default:
      return DiagnosticSeverity.Information
  }
}

/**
 * Create range for a finding (line-based, full line)
 */
function createRange(lineNumber: number, lineContent?: string): Range {
  const line = lineNumber - 1 // LSP is 0-indexed
  const endChar = lineContent ? lineContent.length : 1000

  return {
    start: { line, character: 0 },
    end: { line, character: endChar },
  }
}

/**
 * Format vulnerability as LSP diagnostic
 */
export function formatDiagnostic(finding: Vulnerability, fileUri?: string): Diagnostic {
  const severity = mapSeverity(finding.severity)
  const severityLabel = finding.severity.toUpperCase()

  // Build message with fix suggestion
  let message = `[${severityLabel}] ${finding.title}\n\n${finding.description}`
  if (finding.suggestedFix) {
    message += `\n\n💡 Fix: ${finding.suggestedFix}`
  }

  const diagnostic: Diagnostic = {
    range: createRange(finding.lineNumber, finding.lineContent),
    severity,
    code: finding.category,
    source: 'oculum',
    message,
  }

  // Add validation notes if available
  if (finding.validationNotes) {
    diagnostic.relatedInformation = [{
      location: {
        uri: fileUri || `file://${finding.filePath}`,
        range: createRange(finding.lineNumber),
      },
      message: `AI Validation: ${finding.validationNotes}`,
    }]
  }

  return diagnostic
}

/**
 * Group diagnostics by file URI
 */
export interface DiagnosticsByFile {
  uri: string
  diagnostics: Diagnostic[]
}

/**
 * Format vulnerabilities as diagnostics grouped by file
 */
export function formatDiagnosticsByFile(
  vulnerabilities: Vulnerability[],
  baseUri?: string
): DiagnosticsByFile[] {
  const byFile = new Map<string, Diagnostic[]>()

  for (const finding of vulnerabilities) {
    const uri = baseUri
      ? `${baseUri}/${finding.filePath}`
      : `file://${finding.filePath}`

    if (!byFile.has(uri)) {
      byFile.set(uri, [])
    }

    byFile.get(uri)!.push(formatDiagnostic(finding, uri))
  }

  // Sort diagnostics within each file by line number
  const result: DiagnosticsByFile[] = []
  for (const [uri, diagnostics] of byFile) {
    diagnostics.sort((a, b) => a.range.start.line - b.range.start.line)
    result.push({ uri, diagnostics })
  }

  // Sort files alphabetically
  result.sort((a, b) => a.uri.localeCompare(b.uri))

  return result
}

/**
 * VS Code Code Action (quick fix) format
 */
export interface CodeAction {
  title: string
  kind: string
  diagnostics: Diagnostic[]
  edit?: {
    changes: Record<string, TextEdit[]>
  }
  isPreferred?: boolean
}

/**
 * Text Edit for Code Action
 */
export interface TextEdit {
  range: Range
  newText: string
}

/**
 * Generate code action for a finding (if fix is available)
 */
export function generateCodeAction(
  finding: Vulnerability,
  fileUri: string
): CodeAction | null {
  if (!finding.suggestedFix) return null

  // For now, just provide a "learn more" action
  // In the future, we could generate actual code fixes
  return {
    title: `💡 Fix: ${finding.suggestedFix.slice(0, 50)}${finding.suggestedFix.length > 50 ? '...' : ''}`,
    kind: 'quickfix',
    diagnostics: [formatDiagnostic(finding, fileUri)],
    isPreferred: true,
  }
}

/**
 * Format for VS Code Problems panel (simplified text format)
 */
export function formatForProblemsPanel(vulnerabilities: Vulnerability[]): string {
  const byFile = new Map<string, Vulnerability[]>()

  for (const finding of vulnerabilities) {
    if (!byFile.has(finding.filePath)) {
      byFile.set(finding.filePath, [])
    }
    byFile.get(finding.filePath)!.push(finding)
  }

  let output = ''

  for (const [filePath, findings] of byFile) {
    output += `\n${filePath}\n`
    for (const finding of findings) {
      const icon = finding.severity === 'critical' || finding.severity === 'high' ? '❌' :
                   finding.severity === 'medium' ? '⚠️' : 'ℹ️'
      output += `  ${icon} Line ${finding.lineNumber}: ${finding.title}\n`
    }
  }

  return output.trim()
}
