import { looksLikePatch } from './looks-like-patch.js';
import { convertGitDiffToApplyPatch } from './git-diff.js';
import { convertContextDiffToApplyPatch } from './context-diff.js';
const normalizeBeginEndMarkers = (input) => {
    try {
        const lines = input.replace(/\r\n/g, '\n').split('\n');
        if (!lines.length)
            return input;
        const first = String(lines[0] ?? '').trim().toLowerCase();
        if (first.startsWith('*** begin patch')) {
            lines[0] = '*** Begin Patch';
        }
        const lastIndex = lines.length - 1;
        const last = String(lines[lastIndex] ?? '').trim().toLowerCase();
        if (last.startsWith('*** end patch')) {
            lines[lastIndex] = '*** End Patch';
        }
        return lines.join('\n');
    }
    catch {
        return input;
    }
};
const convertUnifiedDiffToApplyPatchIfPossible = (text) => {
    try {
        if (!text)
            return null;
        if (text.includes('diff --git')) {
            return convertGitDiffToApplyPatch(text);
        }
        const minusMatch = text.match(/^---\s+(.*)$/m);
        const plusMatch = text.match(/^\+\+\+\s+(.*)$/m);
        if (!minusMatch || !plusMatch)
            return null;
        const rawMinus = (minusMatch[1] || '').split('\t')[0] || '';
        const rawPlus = (plusMatch[1] || '').split('\t')[0] || '';
        const normalizeHeaderPath = (value) => {
            const v = String(value || '').trim();
            if (!v)
                return '';
            const m = v.match(/^(?:a\/|b\/)?(.+)$/);
            return (m && m[1] ? m[1] : v).trim();
        };
        const minusPath = normalizeHeaderPath(rawMinus);
        const plusPath = normalizeHeaderPath(rawPlus);
        const filePath = plusPath === '/dev/null' ? minusPath : plusPath;
        if (!filePath)
            return null;
        const synthetic = `diff --git a/${filePath} b/${filePath}\n${text}`;
        return convertGitDiffToApplyPatch(synthetic);
    }
    catch {
        return null;
    }
};
const convertStarHeaderDiffToApplyPatchIfPossible = (text) => {
    try {
        if (!text || text.includes('*** Begin Patch') || text.includes('diff --git'))
            return null;
        const lines = text.replace(/\r\n/g, '\n').split('\n');
        if (lines.length < 3)
            return null;
        const first = String(lines[0] ?? '').trim();
        const second = String(lines[1] ?? '').trim();
        if (!first.startsWith('*** ') || !second.startsWith('--- '))
            return null;
        if (first.startsWith('*** Add File:') || first.startsWith('*** Update File:') || first.startsWith('*** Delete File:')) {
            return null;
        }
        if (!lines.some((line) => line.startsWith('@@')))
            return null;
        const normalizeHeaderPath = (value) => {
            const v = String(value || '').trim().replace(/\s+\*\*\*$/g, '').trim();
            if (!v)
                return '';
            const m = v.match(/^(?:a\/|b\/)?(.+)$/);
            return (m && m[1] ? m[1] : v).trim();
        };
        const oldPath = normalizeHeaderPath(first.slice(4));
        const newPath = normalizeHeaderPath(second.slice(4));
        const body = lines.slice(2);
        const out = ['*** Begin Patch'];
        if (oldPath === '/dev/null' && newPath && newPath !== '/dev/null') {
            out.push(`*** Add File: ${newPath}`);
            for (const line of body) {
                if (line.startsWith('+')) {
                    out.push(line);
                }
            }
            out.push('*** End Patch');
            return out.join('\n');
        }
        if (newPath === '/dev/null' && oldPath && oldPath !== '/dev/null') {
            out.push(`*** Delete File: ${oldPath}`);
            out.push('*** End Patch');
            return out.join('\n');
        }
        const filePath = newPath || oldPath;
        if (!filePath)
            return null;
        out.push(`*** Update File: ${filePath}`);
        for (const line of body) {
            out.push(line);
        }
        out.push('*** End Patch');
        return out.join('\n');
    }
    catch {
        return null;
    }
};
const normalizeApplyPatchHeaderPath = (raw) => {
    let out = String(raw ?? '').trim();
    if (!out)
        return out;
    if ((out.startsWith('"') && out.endsWith('"')) ||
        (out.startsWith("'") && out.endsWith("'")) ||
        (out.startsWith('`') && out.endsWith('`'))) {
        out = out.slice(1, -1).trim();
    }
    return out;
};
const normalizeApplyPatchFileHeader = (line) => {
    const addMatch = line.match(/^\*\*\* Add File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
    if (addMatch && addMatch[1]) {
        return `*** Add File: ${normalizeApplyPatchHeaderPath(addMatch[1])}`;
    }
    const updateMatch = line.match(/^\*\*\* Update File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
    if (updateMatch && updateMatch[1]) {
        return `*** Update File: ${normalizeApplyPatchHeaderPath(updateMatch[1])}`;
    }
    const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
    if (deleteMatch && deleteMatch[1]) {
        return `*** Delete File: ${normalizeApplyPatchHeaderPath(deleteMatch[1])}`;
    }
    return line;
};
const stripCodeFences = (text) => {
    const trimmed = text.trim();
    // Only treat the entire payload as fenced when it *starts* with a code fence.
    // Patch bodies (especially added Markdown files) may legitimately contain ``` blocks;
    // we must not strip those.
    if (!trimmed.startsWith('```'))
        return text;
    const fenceRe = /^```(?:diff|patch|apply_patch|text|json)?[ \t]*\n([\s\S]*?)\n```/gmi;
    const candidates = [];
    let match = null;
    while ((match = fenceRe.exec(trimmed))) {
        if (match[1])
            candidates.push(match[1].trim());
    }
    if (!candidates.length)
        return text;
    for (const candidate of candidates) {
        if (candidate.includes('*** Begin Patch') ||
            candidate.includes('*** Update File:') ||
            candidate.includes('diff --git')) {
            return candidate;
        }
    }
    return candidates[0] ?? text;
};
const decodeEscapedNewlinesIfNeeded = (value) => {
    try {
        if (!value)
            return value;
        if (value.includes('\n'))
            return value;
        if (!value.includes('\\n'))
            return value;
        return value.replace(/\\n/g, '\n');
    }
    catch {
        return value;
    }
};
const stripConflictMarkers = (text) => {
    try {
        const lines = text.replace(/\r\n/g, '\n').split('\n');
        const out = [];
        for (const line of lines) {
            if (line.startsWith('<<<<<<<') || line.startsWith('=======') || line.startsWith('>>>>>>>')) {
                continue;
            }
            out.push(line);
        }
        return out.join('\n');
    }
    catch {
        return text;
    }
};
const splitInlineBeginPatchFileHeader = (text) => {
    if (!text)
        return text;
    return text.replace(/\*\*\*\s*Begin Patch(?:\s*\*\*\*)?\s*(Create|Add|Update|Delete)\s+File:\s*([^\n]+?)(?:\s+\*\*\*)?(?=\n|$)/gi, (_m, actionRaw, pathRaw) => {
        const action = String(actionRaw || '').trim().toLowerCase();
        const mappedAction = action === 'create' ? 'Add' : `${action.charAt(0).toUpperCase()}${action.slice(1)}`;
        const path = String(pathRaw || '').trim();
        return `*** Begin Patch\n*** ${mappedAction} File: ${path}`;
    });
};
export const normalizeApplyPatchText = (raw) => {
    if (!raw)
        return raw;
    let text = raw.replace(/\r\n/g, '\n');
    text = decodeEscapedNewlinesIfNeeded(text);
    text = stripCodeFences(text);
    text = text.trim();
    if (!text)
        return raw;
    text = stripConflictMarkers(text);
    text = splitInlineBeginPatchFileHeader(text);
    text = normalizeBeginEndMarkers(text);
    // Some models emit a GNU unified diff wrapped in apply_patch markers, e.g.:
    //   *** Begin Patch ***
    //   --- a/file
    //   +++ b/file
    //   @@ ...
    //   *** End Patch ***
    // Convert it to apply_patch format so the client tool can execute it.
    if (text.startsWith('*** Begin Patch') &&
        !text.includes('*** Update File:') &&
        !text.includes('*** Add File:') &&
        !text.includes('*** Delete File:') &&
        (/^---\s+/m.test(text) || /^\+\+\+\s+/m.test(text))) {
        const lines = text.split('\n');
        const inner = lines
            .filter((l, idx) => {
            if (idx === 0 && l.startsWith('*** Begin Patch'))
                return false;
            if (idx === lines.length - 1 && l.startsWith('*** End Patch'))
                return false;
            return true;
        })
            .join('\n')
            .trim();
        const converted = convertUnifiedDiffToApplyPatchIfPossible(inner);
        if (converted) {
            text = converted;
        }
    }
    if (!text.includes('*** Begin Patch') && text.includes('diff --git')) {
        const converted = convertGitDiffToApplyPatch(text);
        if (converted)
            text = converted;
    }
    else if (!text.includes('*** Begin Patch') && !text.includes('diff --git')) {
        const converted = convertStarHeaderDiffToApplyPatchIfPossible(text);
        if (converted)
            text = converted;
    }
    if (!text.includes('*** Begin Patch') &&
        !text.includes('diff --git') &&
        text.includes('***************') &&
        /^\*\*\*\s+\S+/m.test(text) &&
        /^---\s+\S+/m.test(text)) {
        // Support classic "context diff" format:
        //   *** path
        //   --- path
        //   ***************
        //   *** 1,2 ****
        //   --- 1,3 ----
        const converted = convertContextDiffToApplyPatch(text);
        if (converted)
            text = converted;
    }
    else if (!text.includes('*** Begin Patch') && !text.includes('diff --git')) {
        const converted = convertUnifiedDiffToApplyPatchIfPossible(text);
        if (converted)
            text = converted;
    }
    text = text.replace(/\*\*\* Create File:/g, '*** Add File:');
    text = text
        .split('\n')
        .map((line) => normalizeApplyPatchFileHeader(line))
        .join('\n');
    let hasBegin = text.includes('*** Begin Patch');
    const hasEnd = text.includes('*** End Patch');
    if (hasBegin && !hasEnd) {
        text = `${text}\n*** End Patch`;
    }
    if (!hasBegin && /^\*\*\* (Add|Update|Delete) File:/m.test(text)) {
        text = `*** Begin Patch\n${text}\n*** End Patch`;
        hasBegin = true;
    }
    if (!text.includes('*** Begin Patch')) {
        return text;
    }
    const beginIndex = text.indexOf('*** Begin Patch');
    if (beginIndex > 0) {
        text = text.slice(beginIndex);
    }
    const endMarker = '*** End Patch';
    const firstEndIndex = text.indexOf(endMarker);
    const concatSignatures = [
        `${endMarker}","input":"*** Begin Patch`,
        `${endMarker}","patch":"*** Begin Patch`,
        `${endMarker}\\",\\"input\\":\\"*** Begin Patch`,
        `${endMarker}\\",\\"patch\\":\\"*** Begin Patch`
    ];
    const hasConcatenationSignal = concatSignatures.some((sig) => text.includes(sig));
    if (hasConcatenationSignal && firstEndIndex >= 0) {
        text = text.slice(0, firstEndIndex + endMarker.length);
    }
    else {
        const lastEndIndex = text.lastIndexOf(endMarker);
        if (lastEndIndex >= 0) {
            const afterEnd = text.slice(lastEndIndex + endMarker.length);
            if (afterEnd.trim().length > 0) {
                text = text.slice(0, lastEndIndex + endMarker.length);
            }
        }
    }
    // Fix missing prefix lines in Update File sections: treat as context (" ").
    const lines = text.split('\n');
    const output = [];
    let inUpdateSection = false;
    let inAddSection = false;
    let afterUpdateHeader = false;
    for (const line of lines) {
        if (line.startsWith('*** Begin Patch')) {
            output.push(line);
            inUpdateSection = false;
            inAddSection = false;
            afterUpdateHeader = false;
            continue;
        }
        if (line.startsWith('*** End Patch')) {
            output.push(line);
            inUpdateSection = false;
            inAddSection = false;
            afterUpdateHeader = false;
            continue;
        }
        if (line.startsWith('*** Update File:')) {
            output.push(line);
            inUpdateSection = true;
            inAddSection = false;
            afterUpdateHeader = true;
            continue;
        }
        if (line.startsWith('*** Add File:')) {
            output.push(line);
            inUpdateSection = false;
            inAddSection = true;
            afterUpdateHeader = false;
            continue;
        }
        if (line.startsWith('*** Delete File:')) {
            output.push(line);
            inUpdateSection = false;
            inAddSection = false;
            afterUpdateHeader = false;
            continue;
        }
        if (inUpdateSection) {
            if (afterUpdateHeader && line.trim() === '') {
                continue;
            }
            afterUpdateHeader = false;
            if (line.startsWith('@@') || line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
                output.push(line);
            }
            else {
                output.push(` ${line}`);
            }
            continue;
        }
        if (inAddSection) {
            if (line.startsWith('+')) {
                output.push(line);
            }
            else {
                output.push(`+${line}`);
            }
            continue;
        }
        output.push(line);
    }
    return output.join('\n');
};
export { looksLikePatch };
