'use client'

import * as React from 'react'
import { FileText, GitBranch, Loader2, RefreshCw } from 'lucide-react'
import { cn } from '../../lib/utils'

export interface DiffSummary {
  totalFiles: number
  staged?: number
  unstaged?: number
}

export interface DiffViewerProps {
  /** A raw unified diff (concatenated `git diff` output). */
  diff: string
  /** The ref/branch the diff is computed against (shown in the header). */
  baseRef?: string
  /** Optional counts summary rendered next to the base ref. */
  summary?: DiffSummary
  /** Raw `git status` text, shown in a collapsible "Working tree status" block. */
  status?: string
  isLoading?: boolean
  error?: string | null
  onRefresh?: () => void
  /** Message shown when the diff is empty. Default "No changes yet". */
  emptyLabel?: string
  className?: string
}

export interface ParsedDiffFile {
  filePath: string
  /** All lines of the file's diff block, including headers. */
  lines: string[]
}

/**
 * Split a raw unified diff into per-file blocks. Ported from dev-hq's GitDiffViewer:
 * split on `diff --git`, derive the file path from the `b/…` side of the header.
 * Pure + exported so it can be unit-tested without rendering.
 */
export function parseDiffFiles(diff: string): ParsedDiffFile[] {
  if (!diff || !diff.trim()) return []
  return diff
    .split(/(?=diff --git)/g)
    .filter((block) => block.trim().length > 0)
    .map((block) => {
      const lines = block.split('\n')
      const header = lines.find((l) => l.startsWith('diff --git'))
      const filePath = header?.match(/ b\/(.+)$/)?.[1] ?? header?.match(/b\/(.+)$/)?.[1] ?? 'unknown'
      return { filePath, lines }
    })
}

/** Tailwind-token class for a single diff line based on its leading character. */
export function diffLineClass(line: string): string {
  if (line.startsWith('@@')) return 'text-cyan-600 dark:text-cyan-400 bg-cyan-500/10 font-semibold'
  if (line.startsWith('+')) return 'text-emerald-600 dark:text-emerald-400 bg-emerald-500/10'
  if (line.startsWith('-')) return 'text-red-600 dark:text-red-400 bg-red-500/10'
  return 'text-muted-foreground'
}

/** Metadata lines that carry no visual value in the rendered diff. */
function isMetadataLine(line: string): boolean {
  return (
    line.startsWith('diff --git') ||
    line.startsWith('index ') ||
    line.startsWith('--- ') ||
    line.startsWith('+++ ') ||
    line.startsWith('new file mode') ||
    line.startsWith('deleted file mode') ||
    line.startsWith('similarity index') ||
    line.startsWith('rename from') ||
    line.startsWith('rename to')
  )
}

/**
 * A presentational unified-diff viewer. It does NO fetching — pass `diff` plus
 * loading/error flags and an `onRefresh` callback; the parent owns the data.
 * Colourised with tailwind tokens (works in light + dark).
 */
export function DiffViewer({
  diff,
  baseRef,
  summary,
  status,
  isLoading = false,
  error = null,
  onRefresh,
  emptyLabel = 'No changes yet',
  className,
}: DiffViewerProps) {
  const files = React.useMemo(() => parseDiffFiles(diff), [diff])

  if (isLoading) {
    return (
      <div
        data-testid="diff-viewer-loading"
        className={cn('flex min-h-[300px] items-center justify-center bg-card', className)}
      >
        <div className="flex flex-col items-center gap-3 text-muted-foreground">
          <Loader2 className="h-8 w-8 animate-spin" aria-hidden="true" />
          <p className="text-sm">Loading diff…</p>
        </div>
      </div>
    )
  }

  if (error) {
    return (
      <div
        data-testid="diff-viewer-error"
        className={cn('flex min-h-[300px] items-center justify-center bg-card', className)}
      >
        <div className="max-w-md text-center">
          <h3 className="mb-2 text-base font-semibold text-foreground">Failed to load diff</h3>
          <p className="mb-4 text-sm text-muted-foreground">{error}</p>
          {onRefresh && (
            <button
              type="button"
              onClick={onRefresh}
              className="inline-flex items-center gap-2 rounded-lg border border-border px-4 py-2 text-sm text-foreground transition-colors hover:bg-accent"
            >
              <RefreshCw className="h-4 w-4" aria-hidden="true" />
              Try again
            </button>
          )}
        </div>
      </div>
    )
  }

  if (files.length === 0) {
    return (
      <div
        data-testid="diff-viewer-empty"
        className={cn('flex min-h-[300px] items-center justify-center bg-card', className)}
      >
        <div className="max-w-md text-center">
          <FileText className="mx-auto mb-4 h-12 w-12 text-muted-foreground" aria-hidden="true" />
          <h3 className="mb-2 text-base font-semibold text-foreground">{emptyLabel}</h3>
          {baseRef && (
            <p className="mb-4 text-sm text-muted-foreground">
              No changes compared to{' '}
              <code className="rounded bg-muted px-1.5 py-0.5 text-foreground">{baseRef}</code>
            </p>
          )}
          {onRefresh && (
            <button
              type="button"
              onClick={onRefresh}
              className="inline-flex items-center gap-2 rounded-lg border border-border px-4 py-2 text-sm text-foreground transition-colors hover:bg-accent"
            >
              <RefreshCw className="h-4 w-4" aria-hidden="true" />
              Refresh
            </button>
          )}
        </div>
      </div>
    )
  }

  return (
    <div className={cn('flex h-full flex-col bg-card', className)}>
      <div className="flex flex-shrink-0 items-center justify-between border-b border-border bg-muted/40 px-4 py-3">
        <div className="flex flex-wrap items-center gap-4">
          {baseRef && (
            <div className="flex items-center gap-2 text-sm">
              <GitBranch className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
              <span className="text-muted-foreground">Base:</span>
              <code className="rounded bg-muted px-1.5 py-0.5 text-foreground">{baseRef}</code>
            </div>
          )}
          {summary && (
            <div className="text-sm text-muted-foreground" data-testid="diff-viewer-summary">
              {summary.totalFiles} {summary.totalFiles === 1 ? 'file' : 'files'} changed
              {summary.staged != null && summary.staged > 0 && (
                <span className="ml-2 text-emerald-600 dark:text-emerald-400">({summary.staged} staged)</span>
              )}
              {summary.unstaged != null && summary.unstaged > 0 && (
                <span className="ml-2 text-amber-600 dark:text-amber-400">({summary.unstaged} unstaged)</span>
              )}
            </div>
          )}
        </div>
        {onRefresh && (
          <button
            type="button"
            onClick={onRefresh}
            title="Refresh diff"
            aria-label="Refresh diff"
            className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
          >
            <RefreshCw className="h-4 w-4" aria-hidden="true" />
          </button>
        )}
      </div>

      {status && (
        <div className="flex-shrink-0 border-b border-border bg-muted/20 px-4 py-2">
          <details className="cursor-pointer">
            <summary className="select-none text-sm text-muted-foreground transition-colors hover:text-foreground">
              Working tree status
            </summary>
            <pre className="mt-2 overflow-x-auto font-mono text-xs text-muted-foreground">{status}</pre>
          </details>
        </div>
      )}

      <div className="flex-1 space-y-4 overflow-auto p-4">
        {files.map((file, fileIndex) => (
          <div
            key={`${file.filePath}-${fileIndex}`}
            data-testid="diff-file"
            className="overflow-hidden rounded-lg border border-border"
          >
            <div className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-2">
              <FileText className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
              <span className="font-mono text-sm text-foreground">{file.filePath}</span>
            </div>
            <pre className="overflow-x-auto p-4 font-mono text-sm">
              {file.lines.map((line, index) =>
                isMetadataLine(line) ? null : (
                  <div key={index} className={diffLineClass(line)}>
                    {line || ' '}
                  </div>
                ),
              )}
            </pre>
          </div>
        ))}
      </div>
    </div>
  )
}
