/**
 * Professional diff renderer component with syntax highlighting
 */
import React, { useMemo } from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../utils/colors.js';
import crypto from 'crypto';
import { MaxSizedBox } from '../shared/max-sized-box.js';
import { highlight } from 'cli-highlight';
export function parseDiffWithLineNumbers(diffContent) {
    const lines = diffContent.split('\n');
    const result = [];
    const state = { oldLine: 0, newLine: 0, inHunk: false };
    for (const line of lines) {
        const hunkInfo = parseHunkHeader(line);
        if (hunkInfo) {
            state.oldLine = hunkInfo.oldLine - 1;
            state.newLine = hunkInfo.newLine - 1;
            state.inHunk = true;
            result.push({ type: 'hunk', content: line });
            continue;
        }
        if (!state.inHunk) {
            if (shouldSkipHeader(line))
                continue;
            continue;
        }
        const diffLine = processDiffLine(line, state);
        if (diffLine) {
            result.push(diffLine);
        }
    }
    return result;
}
function parseHunkHeader(line) {
    const hunkHeaderRegex = /^@@ -(\d+),?\d* \+(\d+),?\d* @@/;
    const match = line.match(hunkHeaderRegex);
    if (match) {
        return {
            oldLine: parseInt(match[1], 10),
            newLine: parseInt(match[2], 10),
        };
    }
    return null;
}
function shouldSkipHeader(line) {
    return (line.startsWith('--- ') ||
        line.startsWith('+++ ') ||
        line.startsWith('diff --git') ||
        line.startsWith('index ') ||
        line.startsWith('similarity index') ||
        line.startsWith('rename from') ||
        line.startsWith('rename to') ||
        line.startsWith('new file mode') ||
        line.startsWith('deleted file mode'));
}
function processDiffLine(line, state) {
    if (line.startsWith('+')) {
        state.newLine++;
        return {
            type: 'add',
            newLine: state.newLine,
            content: line.substring(1),
        };
    }
    if (line.startsWith('-')) {
        state.oldLine++;
        return {
            type: 'del',
            oldLine: state.oldLine,
            content: line.substring(1),
        };
    }
    if (line.startsWith(' ')) {
        state.oldLine++;
        state.newLine++;
        return {
            type: 'context',
            oldLine: state.oldLine,
            newLine: state.newLine,
            content: line.substring(1),
        };
    }
    if (line.startsWith('\\')) {
        return { type: 'other', content: line };
    }
    return null;
}
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
export const DiffRenderer = React.memo(function DiffRenderer({ diffContent, filename, tabWidth = DEFAULT_TAB_WIDTH, availableTerminalHeight, terminalWidth = 80, }) {
    // Memoize parsed content to avoid recomputation
    const parsedLines = useMemo(() => {
        if (!diffContent || typeof diffContent !== 'string') {
            return [];
        }
        // Strip the first summary line (e.g. "Updated file.txt with 1 addition and 2 removals")
        const lines = diffContent.split('\n');
        const firstLine = lines[0];
        let actualDiff = diffContent;
        if (firstLine && (firstLine.startsWith('Updated ') || firstLine.startsWith('Created '))) {
            actualDiff = lines.slice(1).join('\n');
        }
        return parseDiffWithLineNumbers(actualDiff);
    }, [diffContent]);
    if (!diffContent || typeof diffContent !== 'string') {
        return React.createElement(Text, { color: Colors.AccentYellow }, "No diff content.");
    }
    if (parsedLines.length === 0) {
        return React.createElement(Text, { dimColor: true }, "No changes detected.");
    }
    // Always render as diff format to show line numbers and + signs
    const renderedOutput = renderDiffContent(parsedLines, filename, tabWidth, availableTerminalHeight, terminalWidth);
    return React.createElement(React.Fragment, null, renderedOutput);
});
const renderDiffContent = (parsedLines, filename, tabWidth = DEFAULT_TAB_WIDTH, availableTerminalHeight, terminalWidth) => {
    // Detect language for syntax highlighting
    const language = getLanguageFromFilename(filename);
    // 1. Normalize whitespace (replace tabs with spaces) *before* further processing
    const normalizedLines = parsedLines.map((line) => ({
        ...line,
        content: line.content.replace(/\t/g, ' '.repeat(tabWidth)),
    }));
    // Filter out non-displayable lines (hunks, potentially 'other') using the normalized list
    const displayableLines = normalizedLines.filter((l) => l.type !== 'hunk' && l.type !== 'other');
    if (displayableLines.length === 0) {
        return React.createElement(Text, { dimColor: true }, "No changes detected.");
    }
    // Calculate the minimum indentation across all displayable lines
    let baseIndentation = Infinity; // Start high to find the minimum
    for (const line of displayableLines) {
        // Only consider lines with actual content for indentation calculation
        if (line.content.trim() === '')
            continue;
        const firstCharIndex = line.content.search(/\S/); // Find index of first non-whitespace char
        const currentIndent = firstCharIndex === -1 ? 0 : firstCharIndex; // Indent is 0 if no non-whitespace found
        baseIndentation = Math.min(baseIndentation, currentIndent);
    }
    // If baseIndentation remained Infinity (e.g., no displayable lines with content), default to 0
    if (!isFinite(baseIndentation)) {
        baseIndentation = 0;
    }
    const key = filename
        ? `diff-box-${filename}`
        : `diff-box-${crypto.createHash('sha1').update(JSON.stringify(parsedLines)).digest('hex')}`;
    const MAX_CONTEXT_LINES_WITHOUT_GAP = 5;
    let lastLineNumber = null;
    return (React.createElement(MaxSizedBox, { maxHeight: availableTerminalHeight, maxWidth: terminalWidth, key: key }, displayableLines.map((line, index) => {
        const { element, newLastLineNumber } = renderDiffLine(line, index, lastLineNumber, MAX_CONTEXT_LINES_WITHOUT_GAP, terminalWidth, baseIndentation, language);
        lastLineNumber = newLastLineNumber;
        return element;
    })));
};
function renderDiffLine(line, index, lastLineNumber, maxContextLines, terminalWidth, baseIndentation, language) {
    // Determine the relevant line number for gap calculation based on type
    let relevantLineNumberForGapCalc = null;
    if (line.type === 'add' || line.type === 'context') {
        relevantLineNumberForGapCalc = line.newLine ?? null;
    }
    else if (line.type === 'del') {
        // For deletions, the gap is typically in relation to the original file's line numbering
        relevantLineNumberForGapCalc = line.oldLine ?? null;
    }
    const elements = [];
    if (lastLineNumber !== null &&
        relevantLineNumberForGapCalc !== null &&
        relevantLineNumberForGapCalc > lastLineNumber + maxContextLines + 1) {
        elements.push(React.createElement(Box, { key: `gap-${index}` },
            React.createElement(Text, { wrap: "truncate" }, '═'.repeat(terminalWidth))));
    }
    const lineKey = `diff-line-${index}`;
    let gutterNumStr = '';
    let backgroundColor = undefined;
    let prefixSymbol = ' ';
    let dim = false;
    let newLastLineNumber = lastLineNumber;
    switch (line.type) {
        case 'add':
            gutterNumStr = (line.newLine ?? '').toString();
            backgroundColor = '#86efac'; // Light green for additions
            prefixSymbol = '+';
            newLastLineNumber = line.newLine ?? null;
            break;
        case 'del':
            gutterNumStr = (line.oldLine ?? '').toString();
            backgroundColor = 'redBright'; // Light red for deletions
            prefixSymbol = '-';
            // For deletions, update lastLineNumber based on oldLine if it's advancing.
            if (line.oldLine !== undefined) {
                newLastLineNumber = line.oldLine;
            }
            break;
        case 'context':
            gutterNumStr = (line.newLine ?? '').toString();
            dim = true;
            prefixSymbol = ' ';
            newLastLineNumber = line.newLine ?? null;
            break;
        default:
            return { element: null, newLastLineNumber };
    }
    const displayContent = line.content.substring(baseIndentation);
    // Apply syntax highlighting for context lines
    const highlightedContent = line.type === 'context' && language
        ? highlightCode(displayContent, language)
        : displayContent;
    elements.push(React.createElement(Box, { key: lineKey, flexDirection: "row" },
        React.createElement(Text, { color: Colors.Gray, dimColor: dim }, gutterNumStr.padEnd(4)),
        React.createElement(Text, { color: backgroundColor ? '#000000' : undefined, backgroundColor: backgroundColor, dimColor: !backgroundColor && dim },
            prefixSymbol,
            ' '),
        React.createElement(Text, { color: backgroundColor ? '#000000' : undefined, backgroundColor: backgroundColor, dimColor: !backgroundColor && dim, wrap: "wrap" }, highlightedContent)));
    return { element: React.createElement(React.Fragment, { key: `frag-${index}` }, elements), newLastLineNumber };
}
// Language detection utility for syntax highlighting
const getLanguageFromFilename = (filename) => {
    if (!filename)
        return null;
    const extension = filename.split('.').pop()?.toLowerCase() || '';
    const languageMap = {
        js: 'javascript',
        jsx: 'javascript',
        ts: 'typescript',
        tsx: 'typescript',
        py: 'python',
        json: 'json',
        css: 'css',
        scss: 'scss',
        less: 'less',
        html: 'html',
        htm: 'html',
        sh: 'bash',
        bash: 'bash',
        zsh: 'bash',
        md: 'markdown',
        yaml: 'yaml',
        yml: 'yaml',
        txt: 'plaintext',
        java: 'java',
        c: 'c',
        cpp: 'cpp',
        h: 'c',
        hpp: 'cpp',
        rb: 'ruby',
        go: 'go',
        rs: 'rust',
        php: 'php',
        sql: 'sql',
        swift: 'swift',
        kt: 'kotlin',
        scala: 'scala',
        r: 'r',
        lua: 'lua',
        dockerfile: 'dockerfile',
        makefile: 'makefile',
        xml: 'xml',
        toml: 'toml',
        ini: 'ini',
        env: 'bash',
    };
    // Handle special filenames
    const lowerFilename = filename.toLowerCase();
    if (lowerFilename === 'dockerfile')
        return 'dockerfile';
    if (lowerFilename === 'makefile')
        return 'makefile';
    if (lowerFilename.endsWith('.env'))
        return 'bash';
    return languageMap[extension] || null;
};
/**
 * Apply syntax highlighting to code content
 */
const highlightCode = (content, language) => {
    if (!language || !content.trim())
        return content;
    try {
        return highlight(content, { language, ignoreIllegals: true });
    }
    catch {
        return content;
    }
};
//# sourceMappingURL=DiffRenderer.js.map