import React, { useMemo, useState } from 'react';
import { diffLines } from 'diff';
import { IconCheck, IconX, IconCode, IconChevronDown, IconChevronUp } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const CONTEXT_LINES = 2;
const EXPAND_CHUNK_SIZE = 20;
const DiffView = ({ originalCode, newCode, onAccept, onReject, status, contentTypeLabel, warning, disableAccept }) => {
const [isExpanded, setIsExpanded] = useState(true);
const [expandedFromTop, setExpandedFromTop] = useState({});
const [expandedFromBottom, setExpandedFromBottom] = useState({});
const diffResult = useMemo(() => {
const changes = diffLines(originalCode || '', newCode || '');
let additions = 0;
let deletions = 0;
let lineNumber = 1;
const lines = changes.flatMap((part) => {
const partLines = part.value.split('\n');
if (partLines[partLines.length - 1] === '') partLines.pop();
return partLines.map((line) => {
const entry = { content: line, lineNumber: null };
if (part.added) {
additions += 1;
entry.type = 'added';
entry.lineNumber = lineNumber++;
} else if (part.removed) {
deletions += 1;
entry.type = 'removed';
} else {
entry.type = 'unchanged';
entry.lineNumber = lineNumber++;
}
return entry;
});
});
return { lines, additions, deletions };
}, [originalCode, newCode]);
const hunks = useMemo(() => {
const { lines } = diffResult;
if (lines.length === 0) return [];
const changedIndices = new Set();
lines.forEach((line, idx) => {
if (line.type === 'added' || line.type === 'removed') changedIndices.add(idx);
});
const visibleIndices = new Set();
changedIndices.forEach((idx) => {
for (let i = Math.max(0, idx - CONTEXT_LINES); i <= Math.min(lines.length - 1, idx + CONTEXT_LINES); i++) {
visibleIndices.add(i);
}
});
const result = [];
let i = 0;
while (i < lines.length) {
if (visibleIndices.has(i)) {
result.push({ type: 'line', data: lines[i], index: i });
i += 1;
} else {
const start = i;
while (i < lines.length && !visibleIndices.has(i)) i += 1;
result.push({
type: 'collapsed',
startIndex: start,
count: i - start,
lines: lines.slice(start, i)
});
}
}
return result;
}, [diffResult]);
const expandUp = (startIndex, totalLines) => {
setExpandedFromTop((prev) => {
const current = prev[startIndex] || 0;
const bottomExpanded = expandedFromBottom[startIndex] || 0;
const remaining = totalLines - current - bottomExpanded;
return { ...prev, [startIndex]: Math.min(current + EXPAND_CHUNK_SIZE, current + remaining) };
});
};
const expandDown = (startIndex, totalLines) => {
setExpandedFromBottom((prev) => {
const current = prev[startIndex] || 0;
const topExpanded = expandedFromTop[startIndex] || 0;
const remaining = totalLines - topExpanded - current;
return { ...prev, [startIndex]: Math.min(current + EXPAND_CHUNK_SIZE, current + remaining) };
});
};
if (diffResult.additions === 0 && diffResult.deletions === 0) return null;
const renderActions = () => {
if (status === 'accepted') {
return (