using System.Diagnostics;
using System.Text;

namespace StabilityMatrix.Core.Processes;

/// <summary>
/// Process supporting parsing of ANSI escape sequences
/// </summary>
public class AnsiProcess : Process
{
    private AsyncStreamReader? stdoutReader;
    private AsyncStreamReader? stderrReader;

    public AnsiProcess(ProcessStartInfo startInfo)
    {
        StartInfo = startInfo;
        EnableRaisingEvents = false;

        StartInfo.UseShellExecute = false;
        StartInfo.CreateNoWindow = true;
        StartInfo.RedirectStandardOutput = true;
        StartInfo.RedirectStandardInput = true;
        StartInfo.RedirectStandardError = true;

        // Need this to parse ANSI escape sequences correctly
        StartInfo.StandardOutputEncoding = new UTF8Encoding(false);
        StartInfo.StandardErrorEncoding = new UTF8Encoding(false);
        StartInfo.StandardInputEncoding = new UTF8Encoding(false);
    }

    /// <summary>
    /// Start asynchronous reading of stdout and stderr
    /// </summary>
    /// <param name="callback">Called on each new line</param>
    public void BeginAnsiRead(Action<ProcessOutput> callback)
    {
        var stdoutStream = StandardOutput.BaseStream;
        stdoutReader = new AsyncStreamReader(
            stdoutStream,
            s =>
            {
                if (s == null)
                    return;
                callback(ProcessOutput.FromStdOutLine(s));
            },
            StandardOutput.CurrentEncoding
        );

        var stderrStream = StandardError.BaseStream;
        stderrReader = new AsyncStreamReader(
            stderrStream,
            s =>
            {
                if (s == null)
                    return;
                callback(ProcessOutput.FromStdErrLine(s));
            },
            StandardError.CurrentEncoding
        );

        stdoutReader.BeginReadLine();
        stderrReader.BeginReadLine();
    }

    /// <summary>
    /// Waits for output readers to finish
    /// </summary>
    public async Task WaitUntilOutputEOF(CancellationToken ct = default)
    {
        if (stdoutReader is not null)
        {
            await stdoutReader.EOF.WaitAsync(ct).ConfigureAwait(false);
        }
        if (stderrReader is not null)
        {
            await stderrReader.EOF.WaitAsync(ct).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// Cancels stream readers, no effect if already cancelled
    /// </summary>
    public void CancelStreamReaders()
    {
        stdoutReader?.CancelOperation();
        stderrReader?.CancelOperation();
    }

    protected override void Dispose(bool disposing)
    {
        CancelStreamReaders();
        stdoutReader?.Dispose();
        stdoutReader = null;
        stderrReader?.Dispose();
        stderrReader = null;
        base.Dispose(disposing);
    }
}
