using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Iciclecreek.Terminal;

namespace ClaudeDo.Ui.Services;

/// <summary>
/// Thin wrapper around <see cref="TerminalControl.LaunchProcess()"/> — the library owns the
/// Porta.Pty spawn, keyboard input, rendering, resize, and focus end to end (see
/// <c>spikes/ConPtyTerminal/MainWindow.axaml.cs</c>, which proved this renders correctly and
/// stays responsive). We only:
/// 1) apply <see cref="TerminalLaunchDescriptor.Env"/> onto the current process environment
///    before launching — Porta.Pty inherits the calling process's environment and there is no
///    per-launch env seam on <see cref="TerminalControl"/>/<see cref="TerminalControl.LaunchProcess()"/> —
/// 2) relay the control's own <see cref="TerminalControl.ProcessExited"/> event and
///    <see cref="TerminalControl.Kill()"/> method.
/// </summary>
public sealed class PtyTerminalSession : IDisposable
{
    private TerminalControl? _control;
    private bool _disposed;

    public bool IsRunning { get; private set; }
    public int? ExitCode { get; private set; }

    /// <summary>Raised when the child process exits, on the UI thread, with its exit code.</summary>
    public event EventHandler<int>? ProcessExited;

    /// <summary>
    /// Applies <paramref name="descriptor"/>'s env vars to the current process, then drives
    /// <paramref name="control"/> to launch it via <see cref="TerminalControl.LaunchProcess()"/>.
    /// </summary>
    public async Task StartAsync(TerminalLaunchDescriptor descriptor, TerminalControl control, CancellationToken ct = default)
    {
        if (_control is not null) throw new InvalidOperationException("Session already started.");
        _control = control;
        control.ProcessExited += OnControlProcessExited;

        foreach (var (key, value) in descriptor.Env)
            Environment.SetEnvironmentVariable(key, value);

        control.Process = descriptor.Exe;
        control.Args = new List<string>(descriptor.Args);
        control.StartingDirectory = descriptor.Cwd;

        await control.LaunchProcess();
        IsRunning = true;
    }

    private void OnControlProcessExited(object? sender, ProcessExitedEventArgs e)
    {
        IsRunning = false;
        ExitCode = e.ExitCode;
        ProcessExited?.Invoke(this, e.ExitCode);
    }

    public void Kill()
    {
        try { _control?.Kill(); }
        catch (Exception) { /* already exited */ }
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;

        if (_control is not null)
            _control.ProcessExited -= OnControlProcessExited;
    }
}
