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

namespace ClaudeDo.Ui.ViewModels.Modals;

public enum WorktreeDiffLineKind { Header, Hunk, Added, Removed, Context }

public sealed partial class WorktreeDiffLineViewModel : ViewModelBase
{
    public required string Text { get; init; }
    public required WorktreeDiffLineKind Kind { get; init; }
}

public sealed partial class WorktreeNodeViewModel : ViewModelBase
{
    public required string Name { get; init; }
    public string? Status { get; init; }
    public bool IsDirectory { get; init; }
    public string RelativePath { get; init; } = "";
    public ObservableCollection<WorktreeNodeViewModel> Children { get; } = new();
    [ObservableProperty] private bool _isExpanded = true;
}

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

    public ObservableCollection<WorktreeNodeViewModel> Root { get; } = new();
    public ObservableCollection<WorktreeDiffLineViewModel> SelectedFileDiffLines { get; } = new();

    [ObservableProperty] private string _worktreePath = "";
    [ObservableProperty] private string? _baseCommit;
    [ObservableProperty] private WorktreeNodeViewModel? _selectedNode;

    // Set by the view (same pattern as DiffModalViewModel.CloseAction)
    public Action? CloseAction { get; set; }

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

    partial void OnSelectedNodeChanged(WorktreeNodeViewModel? value)
    {
        _ = LoadFileDiffAsync(value);
    }

    private async Task LoadFileDiffAsync(WorktreeNodeViewModel? node)
    {
        SelectedFileDiffLines.Clear();

        if (node is null || node.IsDirectory || string.IsNullOrEmpty(node.RelativePath))
            return;

        string diff;
        try
        {
            diff = await _git.GetFileDiffAsync(WorktreePath, BaseCommit, node.RelativePath);
        }
        catch
        {
            return;
        }

        foreach (var line in diff.Split('\n'))
        {
            var kind = line switch
            {
                _ when line.StartsWith("+++") || line.StartsWith("---") => WorktreeDiffLineKind.Header,
                _ when line.StartsWith("@@")                            => WorktreeDiffLineKind.Hunk,
                _ when line.StartsWith('+')                             => WorktreeDiffLineKind.Added,
                _ when line.StartsWith('-')                             => WorktreeDiffLineKind.Removed,
                _ when line.StartsWith("diff ") || line.StartsWith("index ") || line.StartsWith("\\ ") => WorktreeDiffLineKind.Header,
                _                                                       => WorktreeDiffLineKind.Context,
            };
            SelectedFileDiffLines.Add(new WorktreeDiffLineViewModel { Text = line, Kind = kind });
        }
    }

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

    public async Task LoadAsync(CancellationToken ct = default)
    {
        Root.Clear();

        string stdout;
        bool committedMode = !string.IsNullOrEmpty(BaseCommit);
        try
        {
            stdout = committedMode
                ? await _git.GetCommittedFilesAsync(WorktreePath, BaseCommit!, ct)
                : await _git.GetStatusPorcelainAsync(WorktreePath, ct);
        }
        catch { return; }

        if (string.IsNullOrWhiteSpace(stdout)) return;

        var dirs = new Dictionary<string, WorktreeNodeViewModel>(StringComparer.Ordinal);

        foreach (var line in stdout.Split('\n', StringSplitOptions.RemoveEmptyEntries))
        {
            string? path;
            string? status;

            if (committedMode)
            {
                // diff --name-status format: <status>\t<path>
                var tab = line.IndexOf('\t');
                if (tab < 0) continue;
                var statusChar = line[0];
                status = statusChar != ' ' ? statusChar.ToString() : null;
                path = line[(tab + 1)..].Trim().Replace('\\', '/');
            }
            else
            {
                // porcelain format: XY<space>path
                if (line.Length < 4) continue;
                var xy = line[..2];
                var statusChar = xy[0] != ' ' ? xy[0] : xy[1];
                status = statusChar != ' ' ? statusChar.ToString() : null;
                path = line[3..].Trim().Replace('\\', '/');
            }

            var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length == 0) continue;

            WorktreeNodeViewModel? 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 WorktreeNodeViewModel { Name = segments[i], IsDirectory = true };
                    dirs[accumulated] = dir;
                    if (parent == null) Root.Add(dir);
                    else parent.Children.Add(dir);
                }
                parent = dir;
            }

            var leaf = new WorktreeNodeViewModel
            {
                Name = segments[^1],
                Status = status,
                IsDirectory = false,
                RelativePath = path
            };
            if (parent == null) Root.Add(leaf);
            else parent.Children.Add(leaf);
        }

        SelectedNode = FindFirstLeaf(Root);
    }

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