using System;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Ui.Services;

namespace ClaudeDo.Ui.ViewModels.MissionControl;

/// <summary>
/// Command Center pane hosting an embedded ConPTY terminal for either one task's interactive
/// Claude session, or an ad-hoc/free session in a user-chosen directory (no task, <see cref="TaskId"/>
/// is null — ad-hoc panes are never deduped, unlike task-based ones).
/// </summary>
public sealed partial class ConPtyPaneViewModel : ViewModelBase, IDisposable
{
    public string? TaskId { get; }

    // Only a task-based pane can be submitted for review (an ad-hoc directory session has no task).
    public bool IsTaskBased => TaskId is not null;

    [ObservableProperty] private string _displayTitle;

    [ObservableProperty] private InteractiveTerminalViewModel _terminal = new();

    /// <summary>Set by the host (Mission Control) while a submit-for-review round trip is in
    /// flight, so a rapid double-click can't race two submissions for the same task.</summary>
    [ObservableProperty] private bool _isSubmitPending;

    /// <summary>Raised when the terminal failed to start — the host surfaces this via the footer error strip.</summary>
    public event Action<string>? ErrorReported;

    /// <summary>Set by the host (Mission Control) to remove this pane from its collection.</summary>
    public Action<ConPtyPaneViewModel>? CloseRequested { get; set; }

    /// <summary>Raised when the user submits this task's hand-driven work for review; the host
    /// commits the worktree and moves the task to WaitingForReview.</summary>
    public event Action<string>? SubmitForReviewRequested;

    /// <summary>Task-based pane — dedup'd by <see cref="TaskId"/>. Pass null for an ad-hoc pane
    /// (no task, never deduped); prefer <see cref="CreateAdHoc"/> at ad-hoc call sites.
    /// <paramref name="descriptorFactory"/> is resolved by the pane itself (in <see cref="Start"/>)
    /// so the tile — and its starting spinner — is visible while the launch spec is still being
    /// fetched. The host must wire its handlers and then call <see cref="Start"/>.</summary>
    public ConPtyPaneViewModel(
        string? taskId,
        string displayTitle,
        Func<System.Threading.Tasks.Task<TerminalLaunchDescriptor>> descriptorFactory)
    {
        TaskId = taskId;
        _displayTitle = displayTitle;
        _descriptorFactory = descriptorFactory;
        Terminal.PropertyChanged += OnTerminalPropertyChanged;
    }

    private readonly Func<System.Threading.Tasks.Task<TerminalLaunchDescriptor>> _descriptorFactory;

    /// <summary>Resolves the launch spec and spawns the session. Call after wiring
    /// <see cref="ErrorReported"/> so a failed launch is not swallowed.</summary>
    public void Start() => _ = StartAsync();

    private async System.Threading.Tasks.Task StartAsync()
    {
        try
        {
            Terminal.Start(await _descriptorFactory());
        }
        catch (Exception ex)
        {
            Terminal.ReportStartFailure(ex.Message);
        }
    }

    /// <summary>Ad-hoc pane — no task, no dedup.</summary>
    public static ConPtyPaneViewModel CreateAdHoc(
        string displayTitle,
        Func<System.Threading.Tasks.Task<TerminalLaunchDescriptor>> descriptorFactory)
        => new(null, displayTitle, descriptorFactory);

    private void OnTerminalPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(InteractiveTerminalViewModel.StartError) && Terminal.StartError is { Length: > 0 } error)
            ErrorReported?.Invoke(error);

        if (e.PropertyName is nameof(InteractiveTerminalViewModel.IsStarting)
            or nameof(InteractiveTerminalViewModel.StartError)
            or nameof(InteractiveTerminalViewModel.HasExited))
        {
            SubmitForReviewCommand.NotifyCanExecuteChanged();
            RetryCommand.NotifyCanExecuteChanged();
        }
    }

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

    private bool CanSubmitForReview() =>
        IsTaskBased && !IsSubmitPending && !Terminal.IsStarting && Terminal.StartError is null && !Terminal.HasExited;

    [RelayCommand(CanExecute = nameof(CanSubmitForReview))]
    private void SubmitForReview()
    {
        if (TaskId is { } id) SubmitForReviewRequested?.Invoke(id);
    }

    partial void OnIsSubmitPendingChanged(bool value) => SubmitForReviewCommand.NotifyCanExecuteChanged();

    // A launch failure permanently occupies the TaskId dedupe slot unless the user can retry —
    // re-opening the same task would otherwise just re-focus a dead tile.
    private bool CanRetry() => Terminal.HasExited && Terminal.StartError is not null;

    [RelayCommand(CanExecute = nameof(CanRetry))]
    private void Retry()
    {
        Terminal.PropertyChanged -= OnTerminalPropertyChanged;
        Terminal.Dispose();
        Terminal = new InteractiveTerminalViewModel();
        Terminal.PropertyChanged += OnTerminalPropertyChanged;
        Start();
    }

    public void Dispose()
    {
        Terminal.PropertyChanged -= OnTerminalPropertyChanged;
        Terminal.Kill();
        Terminal.Dispose();
    }
}
