using System.Collections.ObjectModel;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace ClaudeDo.Ui.ViewModels.Modals;

public sealed partial class MergeModalViewModel : ViewModelBase
{
    private readonly IWorkerClient _worker;
    private readonly MergeCoordinator _merge;

    public string TaskId { get; set; } = "";
    public string TaskTitle { get; set; } = "";

    public ObservableCollection<string> Branches { get; } = new();

    [ObservableProperty][NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private string? _selectedBranch;
    [ObservableProperty] private bool _removeWorktree = true;
    [ObservableProperty] private string _commitMessage = "";

    [ObservableProperty][NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private bool _isBusy;

    /// What the pending merge is doing right now, fed by the worker's MergeProgress broadcast.
    /// A merge whose list has a verify command can occupy this call for minutes — without this the
    /// modal only greys the button out and looks dead.
    [ObservableProperty] private string? _progressMessage;

    [ObservableProperty] private string? _errorMessage;
    [ObservableProperty] private string? _warningMessage;
    [ObservableProperty] private string? _successMessage;
    [ObservableProperty][NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private bool _hasConflict;
    [ObservableProperty] private IReadOnlyList<string> _conflictFiles = Array.Empty<string>();

    public Action? CloseAction { get; set; }

    /// True once a merge has succeeded — lets the caller (e.g. the diff window)
    /// close itself after this modal closes.
    public bool Merged { get; private set; }

    /// True once a conflict has been handed off to the resolver — also a cue to close the diff window.
    public bool RoutedToResolver { get; private set; }

    public MergeModalViewModel(IWorkerClient worker, MergeCoordinator merge)
    {
        _worker = worker;
        _merge = merge;
    }

    public async Task InitializeAsync(string taskId, string taskTitle)
    {
        TaskId = taskId;
        TaskTitle = taskTitle;
        CommitMessage = Loc.T("vm.merge.commitMessage", taskTitle);

        IsBusy = true;
        try
        {
            var targets = await _worker.GetMergeTargetsAsync(taskId);
            Branches.Clear();
            if (targets is null)
            {
                ErrorMessage = Loc.T("vm.merge.workerOfflineBranches");
                return;
            }
            // The worker owns the default message — only it knows the task's commit type and the
            // list name the scope is slugged from. The locale string stays as the offline fallback.
            if (!string.IsNullOrWhiteSpace(targets.DefaultCommitMessage))
                CommitMessage = targets.DefaultCommitMessage;
            foreach (var b in targets.LocalBranches) Branches.Add(b);
            SelectedBranch = Branches.Contains(targets.DefaultBranch)
                ? targets.DefaultBranch
                : Branches.FirstOrDefault();
        }
        catch (Exception ex)
        {
            ErrorMessage = Loc.T("vm.merge.loadBranchesFailed", ex.Message);
        }
        finally { IsBusy = false; }
    }

    private bool CanSubmit() =>
        !IsBusy && !HasConflict && !string.IsNullOrWhiteSpace(SelectedBranch);

    [RelayCommand(CanExecute = nameof(CanSubmit))]
    private async Task SubmitAsync()
    {
        if (string.IsNullOrWhiteSpace(SelectedBranch)) return;
        IsBusy = true;
        ErrorMessage = null;
        WarningMessage = null;
        SuccessMessage = null;
        // Subscribed only for the duration of the call: the worker's broadcast reaches every
        // client, and a transient VM left on that event would outlive its window.
        ProgressMessage = Loc.T("vm.merge.progressMerging");
        _worker.MergeProgressEvent += OnMergeProgress;
        _worker.OperationProgressEvent += OnOperationProgress;
        try
        {
            var result = await _worker.MergeTaskAsync(
                TaskId, SelectedBranch!, RemoveWorktree, CommitMessage);

            switch (result.Status)
            {
                case "merged":
                    Merged = true;
                    SuccessMessage = result.ErrorMessage is not null
                        ? $"Merged with warning: {result.ErrorMessage}"
                        : Loc.T("vm.merge.merged");
                    // Auto-close after a short delay.
                    _ = Task.Run(async () =>
                    {
                        await Task.Delay(1200);
                        Avalonia.Threading.Dispatcher.UIThread.Post(() => CloseAction?.Invoke());
                    });
                    break;
                case "conflict":
                    // MergeTask aborted cleanly; hand the conflict to the in-app 3-pane editor,
                    // which re-starts the merge leaving conflicts in the tree.
                    RoutedToResolver = true;
                    CloseAction?.Invoke();
                    await _merge.ResolveConflictAsync(TaskId, SelectedBranch!);
                    break;
                case "blocked":
                    ErrorMessage = Loc.T("vm.merge.blocked", result.ErrorMessage ?? "");
                    break;
                case "verify_failed":
                    // The merge landed; only the Done transition was withheld. Deliberately not
                    // treated as success -- no auto-close, because the failure text is the whole
                    // point of the gate.
                    ErrorMessage = result.ErrorMessage ?? Loc.T("vm.merge.verifyFailed");
                    break;
                case "untracked_collision":
                    // Nothing landed -- the merge was refused before touching the repo. Show the
                    // real message (it names the colliding path and size); the locale string is
                    // only a fallback.
                    ErrorMessage = result.ErrorMessage ?? Loc.T("vm.merge.untrackedCollision");
                    break;
                default:
                    ErrorMessage = Loc.T("vm.merge.unknownStatus", result.Status);
                    break;
            }
        }
        catch (Exception ex)
        {
            ErrorMessage = Loc.T("vm.merge.mergeFailed", ex.Message);
        }
        finally
        {
            _worker.MergeProgressEvent -= OnMergeProgress;
            _worker.OperationProgressEvent -= OnOperationProgress;
            ProgressMessage = null;
            IsBusy = false;
        }
    }

    private void OnMergeProgress(string taskId, string phase, int elapsedSeconds)
    {
        if (taskId != TaskId) return;
        // Rebasing is handled by OnOperationProgress (needs the branch total, which this
        // elapsed-seconds-only forwarder drops) -- don't overwrite it with the generic fallback.
        if (phase == MergePhaseRebasing) return;
        ProgressMessage = phase switch
        {
            MergePhaseVerifying => Loc.T("vm.merge.progressVerifying", FormatElapsed(elapsedSeconds)),
            _ => Loc.T("vm.merge.progressMerging"),
        };
    }

    // Separate event: MergeProgressEvent forwards elapsed-seconds-only (verifying), but the
    // rebase phase needs the branch count too, which only rides the untrimmed OperationProgress.
    private void OnOperationProgress(string opKey, string phase, int current, int total)
    {
        if (opKey != TaskId || phase != MergePhaseRebasing) return;
        ProgressMessage = total > 0
            ? $"{Loc.T("ops.worker.rebasingAfterMerge")} ({current}/{total})"
            : Loc.T("ops.worker.rebasingAfterMerge");
    }

    /// Mirrors TaskMergeService.PhaseVerifying — a hub payload token, not a display string.
    private const string MergePhaseVerifying = "verifying";

    /// Mirrors TaskMergeService.PhaseRebasing — a hub payload token, not a display string.
    private const string MergePhaseRebasing = "rebasing";

    private static string FormatElapsed(int seconds) =>
        TimeSpan.FromSeconds(Math.Max(0, seconds)).ToString(@"mm\:ss");

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