using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace ClaudeDo.Ui.ViewModels.Conflicts;

/// <summary>
/// One conflict region in a file: the two competing versions (and the merge base when the
/// merge used diff3 style), plus the chosen <see cref="Resolution"/> (null until resolved).
/// </summary>
public sealed partial class MergeConflictBlock : ObservableObject
{
    public string Ours { get; }
    public string? Base { get; }
    public string Theirs { get; }

    [ObservableProperty] private string? _resolution;

    public bool IsResolved => Resolution is not null;
    public bool HasBase => Base is not null;

    public MergeConflictBlock(string ours, string? @base, string theirs)
    {
        Ours = ours;
        Base = @base;
        Theirs = theirs;
    }

    partial void OnResolutionChanged(string? value) => OnPropertyChanged(nameof(IsResolved));

    [RelayCommand] private void AcceptOurs()   => Resolution = Ours;
    [RelayCommand] private void AcceptTheirs() => Resolution = Theirs;
    [RelayCommand] private void AcceptBoth()   => Resolution = Ours + Theirs;
    [RelayCommand] private void AcceptBase()   => Resolution = Base ?? "";
}

/// <summary>An ordered piece of a conflicted file: either stable common text or a conflict block.</summary>
public sealed class MergeFileSegment
{
    public bool IsConflict { get; }
    public string StableText { get; }
    public MergeConflictBlock? Conflict { get; }

    private MergeFileSegment(bool isConflict, string stableText, MergeConflictBlock? conflict)
    {
        IsConflict = isConflict;
        StableText = stableText;
        Conflict = conflict;
    }

    public static MergeFileSegment Stable(string text) => new(false, text, null);
    public static MergeFileSegment FromConflict(MergeConflictBlock block) => new(true, "", block);
}

/// <summary>A conflicted file: its ordered segments (for reassembly) and just its conflict blocks.</summary>
public sealed class MergeFile
{
    public string Path { get; }
    public bool IsBinary { get; }
    public IReadOnlyList<MergeFileSegment> Segments { get; }
    public IReadOnlyList<MergeConflictBlock> Conflicts { get; }

    public MergeFile(string path, bool isBinary, IReadOnlyList<MergeFileSegment> segments)
    {
        Path = path;
        IsBinary = isBinary;
        Segments = segments;
        Conflicts = segments.Where(s => s.IsConflict).Select(s => s.Conflict!).ToList();
    }

    /// <summary>A binary file can't be resolved in-app; a text file is done once every block is resolved.</summary>
    public bool AllResolved => !IsBinary && Conflicts.All(c => c.IsResolved);

    /// <summary>Reassemble the file: stable text verbatim, each conflict replaced by its resolution
    /// (empty when unresolved — the same "empty start" the editor shows; Continue is gated on
    /// <see cref="AllResolved"/> so an unresolved conflict never actually reaches here).</summary>
    public string Compose() => string.Concat(
        Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? "") : s.StableText));

    /// <summary>Left pane document: stable regions verbatim, conflict regions show Ours text.</summary>
    public string OursText => string.Concat(
        Segments.Select(s => s.IsConflict ? s.Conflict!.Ours : s.StableText));

    /// <summary>Right pane document: stable regions verbatim, conflict regions show Theirs text.</summary>
    public string TheirsText => string.Concat(
        Segments.Select(s => s.IsConflict ? s.Conflict!.Theirs : s.StableText));

    /// <summary>Middle (result) pane document: stable regions verbatim, conflict regions show the
    /// chosen Resolution, or empty when unresolved (the editor builds each conflict up from empty).</summary>
    public string ResultText => string.Concat(
        Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? "") : s.StableText));
}
