using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClaudeDo.Data.Git;

/// <summary>
/// One piece of a conflicted file: either common ("stable") text both sides agree on,
/// or a conflict region holding the two — or, with diff3 markers, three — competing versions.
/// </summary>
public sealed record MergeSegment
{
    public bool IsConflict { get; init; }

    /// <summary>Stable text (verbatim, line endings preserved) when <see cref="IsConflict"/> is false.</summary>
    public string Text { get; init; } = "";

    /// <summary>"Ours" side (the target branch) when <see cref="IsConflict"/> is true.</summary>
    public string Ours { get; init; } = "";

    /// <summary>Merge base, present only when the merge used diff3 conflict style; null otherwise.</summary>
    public string? Base { get; init; }

    /// <summary>"Theirs" side (the incoming branch) when <see cref="IsConflict"/> is true.</summary>
    public string Theirs { get; init; } = "";

    /// <summary>1-based line number in the file where this segment starts (the marker line for a conflict).</summary>
    public int StartLine { get; init; } = 1;

    public static MergeSegment Stable(string text, int startLine = 1) => new() { Text = text, StartLine = startLine };

    public static MergeSegment Conflict(string ours, string? @base, string theirs, int startLine = 1) =>
        new() { IsConflict = true, Ours = ours, Base = @base, Theirs = theirs, StartLine = startLine };
}

/// <summary>
/// Parses a conflicted file's text into ordered stable / conflict segments and reassembles it.
/// Reads git conflict markers verbatim, so a file with no markers yields a single stable
/// segment, and reassembling the stable text plus one chosen resolution per conflict
/// round-trips the file exactly (line endings included).
/// </summary>
public static class ConflictMarkerParser
{
    private const string OursMarker   = "<<<<<<<";
    private const string BaseMarker   = "|||||||";
    private const string SepMarker    = "=======";
    private const string TheirsMarker = ">>>>>>>";

    public static IReadOnlyList<MergeSegment> Parse(string fileText)
    {
        var segments = new List<MergeSegment>();
        var lines = SplitKeepLineEndings(fileText);
        var stable = new StringBuilder();
        var stableStartLine = 1;
        var i = 0;

        while (i < lines.Count)
        {
            if (!IsMarker(lines[i], OursMarker))
            {
                if (stable.Length == 0) stableStartLine = i + 1;
                stable.Append(lines[i++]);
                continue;
            }

            if (stable.Length > 0)
            {
                segments.Add(MergeSegment.Stable(stable.ToString(), stableStartLine));
                stable.Clear();
            }

            var conflictStartLine = i + 1;
            i++; // consume "<<<<<<<"
            var ours = new StringBuilder();
            while (i < lines.Count && !IsMarker(lines[i], BaseMarker) && !IsMarker(lines[i], SepMarker))
                ours.Append(lines[i++]);

            string? @base = null;
            if (i < lines.Count && IsMarker(lines[i], BaseMarker))
            {
                i++; // consume "|||||||"
                var baseText = new StringBuilder();
                while (i < lines.Count && !IsMarker(lines[i], SepMarker))
                    baseText.Append(lines[i++]);
                @base = baseText.ToString();
            }

            if (i < lines.Count && IsMarker(lines[i], SepMarker)) i++; // consume "======="

            var theirs = new StringBuilder();
            while (i < lines.Count && !IsMarker(lines[i], TheirsMarker))
                theirs.Append(lines[i++]);

            if (i < lines.Count && IsMarker(lines[i], TheirsMarker)) i++; // consume ">>>>>>>"

            segments.Add(MergeSegment.Conflict(ours.ToString(), @base, theirs.ToString(), conflictStartLine));
        }

        if (stable.Length > 0)
            segments.Add(MergeSegment.Stable(stable.ToString(), stableStartLine));

        return segments;
    }

    /// <summary>True when the file still contains an opening conflict marker.</summary>
    public static bool HasConflicts(string fileText) =>
        SplitKeepLineEndings(fileText).Any(l => IsMarker(l, OursMarker));

    /// <summary>
    /// Reassembles a file from its segments. Stable segments emit their text verbatim;
    /// each conflict segment emits whatever <paramref name="resolveConflict"/> returns for it.
    /// </summary>
    public static string Compose(
        IEnumerable<MergeSegment> segments, Func<MergeSegment, string> resolveConflict) =>
        string.Concat(segments.Select(s => s.IsConflict ? resolveConflict(s) : s.Text));

    // A marker line starts with exactly the 7-char marker, then end-of-line or whitespace/label.
    private static bool IsMarker(string line, string marker)
    {
        if (!line.StartsWith(marker, StringComparison.Ordinal)) return false;
        if (line.Length == marker.Length) return true;
        return line[marker.Length] is ' ' or '\t' or '\r' or '\n';
    }

    // Splits into physical lines, each retaining its trailing "\n" (and "\r" if present).
    private static List<string> SplitKeepLineEndings(string s)
    {
        var lines = new List<string>();
        var i = 0;
        while (i < s.Length)
        {
            var nl = s.IndexOf('\n', i);
            if (nl < 0) { lines.Add(s[i..]); break; }
            lines.Add(s[i..(nl + 1)]);
            i = nl + 1;
        }
        return lines;
    }
}
