using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data.Git;

namespace ClaudeDo.Ui.ViewModels.Modals;

public enum DiffLineKind { Add, Del, Ctx }

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 string ClassName => Kind switch
    {
        DiffLineKind.Add => "add",
        DiffLineKind.Del => "del",
        _                => "ctx",
    };

    public string Sign => Kind switch
    {
        DiffLineKind.Add => "+",
        DiffLineKind.Del => "-",
        _                => " ",
    };
}

public sealed class DiffFileViewModel
{
    public required string Path { get; init; }
    public int Additions { get; set; }
    public int Deletions { get; set; }
    public ObservableCollection<DiffLineViewModel> Lines { get; } = new();
}

public sealed partial class DiffModalViewModel : ViewModelBase
{
    private readonly GitService _git;

    public required string WorktreePath { get; init; }
    public string? BaseRef { get; init; }
    public string? TaskId { get; init; }
    public string TaskTitle { get; init; } = "";
    public Func<MergeModalViewModel, Task>? ShowMergeModal { get; set; }
    public Func<MergeModalViewModel>? ResolveMergeVm { get; set; }

    public ObservableCollection<DiffFileViewModel> Files { get; } = new();

    [ObservableProperty] private DiffFileViewModel? _selectedFile;
    [ObservableProperty] private string? _statusMessage;

    // Injected action to close the owning Window
    public Action? CloseAction { get; set; }

    public DiffModalViewModel(GitService git)
    {
        _git = git;
    }

    [RelayCommand]
    private void Close() => CloseAction?.Invoke();

    private bool CanMerge() =>
        !string.IsNullOrEmpty(TaskId)
        && ShowMergeModal is not null
        && ResolveMergeVm is not null;

    [RelayCommand(CanExecute = nameof(CanMerge))]
    private async Task MergeAsync()
    {
        if (TaskId is null || ShowMergeModal is null || ResolveMergeVm is null) return;
        var vm = ResolveMergeVm();
        await vm.InitializeAsync(TaskId, TaskTitle);
        await ShowMergeModal(vm);
    }

    public async Task LoadAsync(CancellationToken ct = default)
    {
        Files.Clear();
        StatusMessage = null;

        string raw;
        try
        {
            raw = BaseRef is not null
                ? await _git.GetBranchDiffAsync(WorktreePath, BaseRef, ct)
                : await _git.GetDiffAsync(WorktreePath, ct);
        }
        catch (Exception ex)
        {
            StatusMessage = $"Failed to load diff: {ex.Message}";
            return;
        }

        if (string.IsNullOrWhiteSpace(raw))
        {
            StatusMessage = "No changes to show.";
            return;
        }

        // Parse unified diff — state machine over lines
        DiffFileViewModel? current = null;
        int oldLine = 0, newLine = 0;

        foreach (var line in raw.Split('\n'))
        {
            if (line.StartsWith("diff --git ", StringComparison.Ordinal))
            {
                // e.g. "diff --git a/src/Foo.cs b/src/Foo.cs"
                var parts = line.Split(' ');
                var path = parts.Length >= 4 ? parts[3][2..] : line;
                current = new DiffFileViewModel { Path = path };
                Files.Add(current);
                oldLine = 0; newLine = 0;
                continue;
            }

            if (current == null) continue;

            if (line.StartsWith("@@ ", StringComparison.Ordinal))
            {
                // e.g. "@@ -10,7 +10,9 @@"
                ParseHunkHeader(line, out oldLine, out newLine);
                continue;
            }

            // Skip diff metadata lines
            if (line.StartsWith("--- ", StringComparison.Ordinal) ||
                line.StartsWith("+++ ", StringComparison.Ordinal) ||
                line.StartsWith("index ", StringComparison.Ordinal) ||
                line.StartsWith("new file", StringComparison.Ordinal) ||
                line.StartsWith("deleted file", StringComparison.Ordinal) ||
                line.StartsWith("Binary ", StringComparison.Ordinal))
                continue;

            if (line.StartsWith('+'))
            {
                current.Lines.Add(new DiffLineViewModel
                {
                    Kind = DiffLineKind.Add,
                    NewNo = newLine++,
                    Text = line.Length > 1 ? line[1..] : "",
                });
                current.Additions++;
            }
            else if (line.StartsWith('-'))
            {
                current.Lines.Add(new DiffLineViewModel
                {
                    Kind = DiffLineKind.Del,
                    OldNo = oldLine++,
                    Text = line.Length > 1 ? line[1..] : "",
                });
                current.Deletions++;
            }
            else if (line.StartsWith(' '))
            {
                current.Lines.Add(new DiffLineViewModel
                {
                    Kind = DiffLineKind.Ctx,
                    OldNo = oldLine++,
                    NewNo = newLine++,
                    Text = line.Length > 1 ? line[1..] : "",
                });
            }
        }

        SelectedFile = Files.Count > 0 ? Files[0] : null;
        if (Files.Count == 0) StatusMessage = "No changes to show.";
    }

    private static void ParseHunkHeader(string header, out int oldStart, out int newStart)
    {
        oldStart = 1; newStart = 1;
        // Format: @@ -<old>,<count> +<new>,<count> @@
        var at = header.IndexOf("@@", 3, StringComparison.Ordinal);
        var inner = at > 0 ? header[3..at].Trim() : header;
        var segs = inner.Split(' ');
        foreach (var seg in segs)
        {
            if (seg.StartsWith('-') && int.TryParse(seg[1..].Split(',')[0], out var o))
                oldStart = o;
            else if (seg.StartsWith('+') && int.TryParse(seg[1..].Split(',')[0], out var n))
                newStart = n;
        }
    }
}
