using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;

namespace ClaudeDo.Ui.ViewModels.Modals;

// Shared diff models used by UnifiedDiffParser, DiffTextView and DiffViewerViewModel.

public enum DiffLineKind { Add, Del, Ctx, File }

public enum DiffFileStatus { Modified, Added, Deleted, Renamed }

public sealed class DiffLineViewModel
{
    public required DiffLineKind Kind { get; init; }
    public int? OldNo { get; init; }
    public int? NewNo { get; init; }
    public required string Text { get; init; }
}

public sealed class DiffFileViewModel
{
    public required string Path { get; set; }
    public string? OldPath { get; set; }
    public DiffFileStatus Status { get; set; } = DiffFileStatus.Modified;
    public bool IsBinary { get; set; }
    public int Additions { get; set; }
    public int Deletions { get; set; }
    public ObservableCollection<DiffLineViewModel> Lines { get; } = new();

    /// Single-letter badge for the file's change kind (A/M/D/R).
    public string StatusCode => Status switch
    {
        DiffFileStatus.Added    => "A",
        DiffFileStatus.Deleted  => "D",
        DiffFileStatus.Renamed  => "R",
        _                       => "M",
    };

    public bool HasLines => Lines.Count > 0;

    /// A text file that produced no diff hunks (e.g. a newly added empty file).
    public bool IsEmptyContent => !IsBinary && Lines.Count == 0;

    /// A rename with no content change — showing "+0 −0" for it is misleading.
    public bool IsPureRename => Status == DiffFileStatus.Renamed && Additions == 0 && Deletions == 0;

    /// Header path label: for a rename, spell out old → new; otherwise just the path.
    public string HeaderPath => Status == DiffFileStatus.Renamed && !string.IsNullOrEmpty(OldPath)
        ? $"{OldPath} → {Path}"
        : Path;
}

/// One row in the planning subtask list (left pane in Planning mode).
public sealed record SubtaskDiffRow(string Id, string Title, string? DiffStat, string UnifiedDiff);

/// Folder/file node for the file-tree nav (left pane in Files mode). File leaves carry
/// their parsed <see cref="DiffFileViewModel"/> so selection swaps the right pane with no
/// further git calls.
public sealed partial class DiffTreeNodeViewModel : ViewModelBase
{
    public required string Name { get; init; }
    public bool IsDirectory { get; init; }
    public string RelativePath { get; init; } = "";
    public DiffFileViewModel? File { get; init; }
    public ObservableCollection<DiffTreeNodeViewModel> Children { get; } = new();
    [ObservableProperty] private bool _isExpanded = true;

    public string? StatusCode => File?.StatusCode;
    public bool ShowStats => File is { IsBinary: false, IsPureRename: false };
    public int Additions => File?.Additions ?? 0;
    public int Deletions => File?.Deletions ?? 0;
}

/// Builds a folder-grouped tree from a flat list of parsed diff files.
public static class DiffTree
{
    public static List<DiffTreeNodeViewModel> Build(IEnumerable<DiffFileViewModel> files)
    {
        var roots = new List<DiffTreeNodeViewModel>();
        var dirs = new Dictionary<string, DiffTreeNodeViewModel>(StringComparer.Ordinal);

        foreach (var file in files)
        {
            var path = file.Path.Replace('\\', '/');
            var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length == 0) continue;

            DiffTreeNodeViewModel? parent = null;
            var accumulated = "";
            for (var i = 0; i < segments.Length - 1; i++)
            {
                accumulated = accumulated.Length == 0 ? segments[i] : accumulated + "/" + segments[i];
                if (!dirs.TryGetValue(accumulated, out var dir))
                {
                    dir = new DiffTreeNodeViewModel { Name = segments[i], IsDirectory = true, RelativePath = accumulated };
                    dirs[accumulated] = dir;
                    if (parent is null) roots.Add(dir); else parent.Children.Add(dir);
                }
                parent = dir;
            }

            var leaf = new DiffTreeNodeViewModel
            {
                Name = segments[^1],
                IsDirectory = false,
                RelativePath = path,
                File = file,
            };
            if (parent is null) roots.Add(leaf); else parent.Children.Add(leaf);
        }

        return roots;
    }

    public static DiffTreeNodeViewModel? FirstLeaf(IEnumerable<DiffTreeNodeViewModel> nodes)
    {
        foreach (var n in nodes)
        {
            if (!n.IsDirectory) return n;
            var nested = FirstLeaf(n.Children);
            if (nested is not null) return nested;
        }
        return null;
    }
}
