/**
 * Diff computation and rendering for the Provenance view.
 *
 * Uses the `diff` package for line-level diffs with:
 *   - 3-line context around changes
 *   - Collapsed unchanged regions (GitKraken style)
 *   - vimdiff-style coloring (red=removed, green=added)
 */

import { Text } from 'ink';
import { diffLines } from 'diff';

// ── Types ──

export type DiffLineKind = 'added' | 'removed' | 'context' | 'collapsed';

export interface DiffLine {
  kind: DiffLineKind;
  text: string;
  collapsedCount?: number; // only for 'collapsed' kind
}

export interface DiffStats {
  added: number;
  removed: number;
  pctChanged: number;
}

export interface DiffResult {
  lines: DiffLine[];
  stats: DiffStats;
}

// ── Compute diff with context and collapsing ──

const DEFAULT_CONTEXT = 3;

export function computeDiffLines(
  upstream: string,
  transformed: string,
  contextLines = DEFAULT_CONTEXT,
): DiffResult {
  if (upstream === transformed) {
    // Identical — show single collapsed block
    const lineCount = upstream.split('\n').length;
    return {
      lines: [{ kind: 'collapsed', text: `${lineCount} unchanged lines`, collapsedCount: lineCount }],
      stats: { added: 0, removed: 0, pctChanged: 0 },
    };
  }

  const changes = diffLines(upstream, transformed);

  // Step 1: Flatten changes into individual tagged lines
  interface TaggedLine { kind: 'added' | 'removed' | 'unchanged'; text: string }
  const tagged: TaggedLine[] = [];

  for (const change of changes) {
    const lines = change.value.replace(/\n$/, '').split('\n');
    const kind: TaggedLine['kind'] = change.added ? 'added' : change.removed ? 'removed' : 'unchanged';
    for (const line of lines) {
      tagged.push({ kind, text: line });
    }
  }

  // Step 2: Mark which unchanged lines are within context range of a change
  const inContext = new Set<number>();
  for (let i = 0; i < tagged.length; i++) {
    if (tagged[i]!.kind !== 'unchanged') {
      // Mark contextLines before and after
      for (let j = Math.max(0, i - contextLines); j <= Math.min(tagged.length - 1, i + contextLines); j++) {
        if (tagged[j]!.kind === 'unchanged') {
          inContext.add(j);
        }
      }
    }
  }

  // Step 3: Build output with collapsed regions
  const result: DiffLine[] = [];
  let collapsedRun = 0;

  for (let i = 0; i < tagged.length; i++) {
    const line = tagged[i]!;
    if (line.kind !== 'unchanged') {
      // Flush collapsed
      if (collapsedRun > 0) {
        result.push({ kind: 'collapsed', text: `${collapsedRun} unchanged lines`, collapsedCount: collapsedRun });
        collapsedRun = 0;
      }
      result.push({ kind: line.kind, text: line.text });
    } else if (inContext.has(i)) {
      // Context line
      if (collapsedRun > 0) {
        result.push({ kind: 'collapsed', text: `${collapsedRun} unchanged lines`, collapsedCount: collapsedRun });
        collapsedRun = 0;
      }
      result.push({ kind: 'context', text: line.text });
    } else {
      collapsedRun++;
    }
  }

  // Flush trailing collapsed
  if (collapsedRun > 0) {
    result.push({ kind: 'collapsed', text: `${collapsedRun} unchanged lines`, collapsedCount: collapsedRun });
  }

  // Stats
  let added = 0;
  let removed = 0;
  for (const t of tagged) {
    if (t.kind === 'added') added++;
    if (t.kind === 'removed') removed++;
  }
  const total = Math.max(1, tagged.length - added); // original line count approximation
  const pctChanged = Math.round(((added + removed) / (total + added)) * 100);

  return { lines: result, stats: { added, removed, pctChanged } };
}

// ── Format header stats ──

export function formatDiffStats(stats: DiffStats): string {
  if (stats.added === 0 && stats.removed === 0) return 'no changes';
  const parts: string[] = [];
  if (stats.added > 0) parts.push(`+${stats.added}`);
  if (stats.removed > 0) parts.push(`-${stats.removed}`);
  parts.push(`${stats.pctChanged}% chg`);
  return parts.join(' ');
}

// ── Rendering component ──

interface DiffPanelProps {
  lines: DiffLine[];
  scrollOffset: number;
  height: number;
  horizontalOffset?: number;
}

function hSlice(text: string, offset: number): string {
  return offset > 0 ? text.slice(offset) : text;
}

export function DiffPanel({ lines, scrollOffset, height, horizontalOffset = 0 }: DiffPanelProps) {
  const visible = lines.slice(scrollOffset, scrollOffset + height);
  const hasMore = lines.length > scrollOffset + height;

  return (
    <>
      {visible.map((line, i) => {
        switch (line.kind) {
          case 'added':
            return (
              <Text key={i} wrap="truncate" color="green">
                {'+ '}{hSlice(line.text, horizontalOffset)}
              </Text>
            );
          case 'removed':
            return (
              <Text key={i} wrap="truncate" color="red">
                {'- '}{hSlice(line.text, horizontalOffset)}
              </Text>
            );
          case 'context':
            return (
              <Text key={i} wrap="truncate" dimColor>
                {'  '}{hSlice(line.text, horizontalOffset)}
              </Text>
            );
          case 'collapsed':
            return (
              <Text key={i} wrap="truncate" color="gray">
                {'\u2504\u2504\u2504 '}{line.text}{' \u2504\u2504\u2504'}
              </Text>
            );
        }
      })}
      {hasMore && (
        <Text dimColor>{'\u2026'} +{lines.length - scrollOffset - height} more</Text>
      )}
    </>
  );
}
